Fza*_*Fza 4 performance sql-server t-sql sql-server-2016 memory-grant query-performance
我在 SQL Server 2016 EE 实例上有一个相对较大的 550GB 数据库,该实例的最大内存限制为操作系统可用的总 128GB RAM 中的 112GB。数据库的最新兼容级别为 130。开发人员抱怨下面的查询在隔离执行时在他们可接受的 30 秒内执行,但是当他们大规模运行他们的进程时,同一查询会并发执行多次跨多个线程,这是他们观察到执行时间受到影响并且性能/吞吐量下降的时候。有问题的 T-SQL 是:
select distinct dg.entityId, et.EntityName, dg.Version
from DataGathering dg with(nolock)
inner join entity e with(nolock)
on e.EntityId = dg.EntityId
inner join entitytype et with(nolock)
on et.EntityTypeID = e.EntityTypeID
and et.EntityName = 'Account_Third_Party_Details'
inner join entitymapping em with(nolock)
on em.ChildEntityId = dg.EntityId
and em.ParentEntityId = -1
where dg.EntityId = dg.RootId
union all
select distinct dg1.EntityId, et.EntityName, dg1.version
from datagathering dg1 with(nolock)
inner join entity e with(nolock)
on e.EntityId = dg1.EntityId
inner join entitytype et with(nolock)
on et.EntityTypeID = e.EntityTypeID
and et.EntityName = 'TIN_Details'
where dg1.EntityId = dg1.RootId
and dg1.EntityId not in (
select distinct ChildEntityId
from entitymapping
where ChildEntityId = dg1.EntityId
and ParentEntityId = -1)
Run Code Online (Sandbox Code Playgroud)
实际执行计划显示以下内存授予警告:
图形执行计划可以在这里找到:
https://www.brentozar.com/pastetheplan/?id=r18ZtCidN
以下是此查询涉及的表的行数和大小。最昂贵的运算符是对 DataGathering 表上的非聚集索引进行索引扫描,考虑到与其他表相比,该表的大小是有意义的。我理解为什么/如何需要内存授予,我认为这是由于查询的编写方式需要多种排序和散列运算符。我需要的建议/指导是如何避免内存授予、T-SQL 和重构代码不是我的强项,有没有办法重写此查询以使其性能更高?如果我可以调整查询以使其独立运行得更快,那么希望好处会转移到大规模运行时,即性能开始受到影响的时候。很高兴提供更多信息,并希望从中学到一些东西!
更新 3 个表的统计信息后:
UPDATE STATISTICS Entity WITH FULLSCAN;
UPDATE STATISTICS EntityMapping WITH FULLSCAN;
UPDATE STATISTICS EntityType WITH FULLSCAN;
Run Code Online (Sandbox Code Playgroud)
...执行计划改进了一些:
https://www.brentozar.com/pastetheplan/?id=rkVmdkh_4
不幸的是,“过度授予”警告仍然存在。
Josh Darnell 善意地建议将查询重新分解为以下内容,以避免他在某个运算符上发现的并行性被抑制。重构的查询抛出错误“消息 4104,级别 16,状态 1,第 7 行无法绑定多部分标识符“et.EntityName”。” 我该如何解决这个问题?
DECLARE @tinDetailsId int;
SELECT @tinDetailsId = et.EntityTypeID
FROM entitytype et
WHERE et.EntityName = 'TIN_Details';
select distinct dg1.EntityId, et.EntityName, dg1.version
from datagathering dg1 with(nolock)
inner join entity e with(nolock)
on e.EntityId = dg1.EntityId
where dg1.EntityId = dg1.RootId
and e.EntityTypeID = @tinDetailsId
and dg1.EntityId not in (
select distinct ChildEntityId
from entitymapping
where ChildEntityId = dg1.EntityId
and ParentEntityId = -1)
UNION ALL
select distinct dg.entityId, et.EntityName, dg.Version
from DataGathering dg with(nolock)
inner join entity e with(nolock)
on e.EntityId = dg.EntityId
inner join entitytype et with(nolock)
on et.EntityTypeID = e.EntityTypeID
and et.EntityName = 'Account_Third_Party_Details'
inner join entitymapping em with(nolock)
on em.ChildEntityId = dg.EntityId
and em.ParentEntityId = -1
where dg.EntityId = dg.RootId
Run Code Online (Sandbox Code Playgroud)
这可能对内存授予情况没有帮助(希望额外的统计信息更新会对此有所帮助),但我注意到此查询中的并行性被抑制。查看计划的这一部分:
由于嵌套循环连接的外侧只有一行,因此所有 900k 行都汇集到一个线程上。因此,尽管此查询在 DOP 8 上运行,但计划的这一部分是完全串行的。这包括排序。这是这种类型的 XML:
如果可能,请考虑避免连接到 EntityType,而只是获取该 Id 并使用它过滤 Entity 表。这将允许它只是实体表的索引扫描的谓词,希望允许并行并加快执行速度。
像这样的东西:
DECLARE @tinDetailsId int;
SELECT @tinDetailsId = et.EntityTypeID
FROM entitytype et
WHERE et.EntityName = 'TIN_Details';
Run Code Online (Sandbox Code Playgroud)
然后您可以在查询的下半部分引用,消除连接:
select distinct dg1.EntityId, 'TIN_Details', dg1.version
from datagathering dg1 with(nolock)
inner join entity e with(nolock)
on e.EntityId = dg1.EntityId
where dg1.EntityId = dg1.RootId
and e.EntityTypeID = @tinDetailsId
and dg1.EntityId not in (
select distinct ChildEntityId
from entitymapping
where ChildEntityId = dg1.EntityId
and ParentEntityId = -1)
Run Code Online (Sandbox Code Playgroud)
您可能希望对EntityName
查询顶部的“Account_Third_Party_Details”做同样的事情,因为它有同样的问题——行数是原来的两倍。
PS:与手头的主题完全无关,我注意到您nolock
对此查询中的所有表都有提示。确保您了解这的含义。查看有关该主题的这篇漂亮的博客文章:
坏习惯: Aaron Bertrand把 NOLOCK 放在任何地方Paul White
的 Read Uncommitted Isolation Level