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)
実際の実行計画は、以下のメモリ許可警告を示しています。
グラフィカルな実行計画はここにあります:
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;
...実行計画によっていくつかが改善されました:
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
これはメモリ許可の状況には役立ちません(統計の更新を追加することでうまくいくと思います)が、このクエリでは並列処理が禁止されていることに気付きました。計画のこの部分を確認してください:
ネストされたループ結合の外側には行が1つしかないため、900k行すべてが1つのスレッドに集められます。したがって、このクエリはDOP 8で実行されていますが、計画のこの部分は完全にシリアルです。ソートも含まれます。これがその種のXMLです。
可能であれば、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を置く アーロン・バートランド
コミットされていない読み取りの分離レベル ポールホワイト