web-dev-qa-db-ja.com

SQL Server 2016-パフォーマンスの低いクエリに対する過剰なメモリ許可警告

SQL Server 2016 EEインスタンスに550GBの比較的大きなデータベースがあります。これは、128GBの合計の最大メモリ制限が112GBですRAM OSで利用可能です。データベースは最新の互換性レベルです開発者は、単独で実行すると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)

実際の実行計画は、以下のメモリ許可警告を示しています。

enter image description here

グラフィカルな実行計画はここにあります:

https://www.brentozar.com/pastetheplan/?id=r18ZtCidN

以下は、このクエリで操作されるテーブルの行数とサイズです。最も高価な演算子は、DataGatheringテーブルの非クラスター化インデックスのインデックススキャンであり、他のテーブルと比較してテーブルのサイズを考えると理にかなっています。メモリの付与が必要な理由/方法を理解しています。これは、複数のソートとハッシュ演算子を必要とするクエリの記述方法が原因であると考えています。私がアドバイス/ガイダンスを必要としているのは、メモリの許可を回避する方法です。T-SQLとコードのリファクタリングは私の強みではありません。このクエリを書き直してよりパフォーマンスを高める方法はありますか?クエリを分離してより速く実行するように調整できれば、うまくいけば、パフォーマンスが低下し始めるスケールで実行されたときにメリットが移転します。さらに情報を提供し、これから何かを学びたいと思っています!

enter image description here

3つのテーブルの統計を更新した後:

UPDATE STATISTICS Entity WITH FULLSCAN; 
UPDATE STATISTICS EntityMapping WITH FULLSCAN; 
UPDATE STATISTICS EntityType WITH FULLSCAN;

...実行計画によっていくつかが改善されました:

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  
3
Fza

これはメモリ許可の状況には役立ちません(統計の更新を追加することでうまくいくと思います)が、このクエリでは並列処理が禁止されていることに気付きました。計画のこの部分を確認してください:

screenshot of plan Explorer window

ネストされたループ結合の外側には行が1つしかないため、900k行すべてが1つのスレッドに集められます。したがって、このクエリはDOP 8で実行されていますが、計画のこの部分は完全にシリアルです。ソートも含まれます。これがその種のXMLです。

screenshot of plan XML showing unbalanced parallelism

可能であれば、EntityTypeへの結合を避け、代わりにそのIdを取得してEntityテーブルをフィルタリングすることを検討してください。これにより、エンティティテーブルのインデックススキャンの述語になり、並列処理が可能になり、実行が高速化されます。

このようなもの:

DECLARE @tinDetailsId int;

SELECT @tinDetailsId = et.EntityTypeID 
FROM entitytype et 
WHERE et.EntityName = 'TIN_Details';

これをクエリの下半分で参照して、結合を排除できます。

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)

同じ問題があるため、クエリの上部にあるEntityName "Account_Third_Party_Details"を使用して同じことを行います。

PS:このトピックとはまったく無関係で、このクエリのすべてのテーブルにnolockヒントがあることに気付きました。これの影響を認識していることを確認してください。トピックに関するこの気の利いたブログ投稿をチェックしてください:

悪い習慣:どこにでもNOLOCKを置く アーロン・バートランド
コミットされていない読み取りの分離レベル ポールホワイト

7
Josh Darnell