SQL Server 2016 - 对性能不佳的查询发出过多的内存授予警告

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)

Jos*_*ell 8

这可能对内存授予情况没有帮助(希望额外的统计信息更新会对此有所帮助),但我注意到此查询中的并行性被抑制。查看计划的这一部分:

计划资源管理器窗口的屏幕截图

由于嵌套循环连接的外侧只有一行,因此所有 900k 行都汇集到一个线程上。因此,尽管此查询在 DOP 8 上运行,但计划的这一部分是完全串行的。这包括排序。这是这种类型的 XML:

显示不平衡并行性的计划 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