Microsoft SQL Server 2016(SP2-CU11)(KB4527378)-13.0.5598.27(X64)2019年11月27日18:09:22著作権(c)Microsoft Corporation Standard Edition(64ビット)on Windows Server 2012 R2を使用しています標準6.3(ビルド9600:)
このサーバーはSSDドライブ上にあり、最大メモリは128 GBです。並列処理のCostThesholdは70、並列処理のMaxDegreeは3です。
ON DELETE CASCADEオプションを指定した23個の外部キーによって参照される「Trips」テーブルがあります。
このテーブル自体はそれほど大きくありません(530万行、1.3 GBのデータ)。ただし、参照される23のテーブルのうち、2つのテーブルは非常に大きい(10億を超える行、それぞれ54および69 GB)。
問題は、「Trips」テーブル内の少量の行(4行としましょう)を削除しようとすると、SQLは非常に多くの行が削除されると推定し、10 GBのRAMを要求し、数百万行が推定されることを示します。戻り、テーブルをロックします。すべてが停止し、他のクエリがブロックされ、アプリケーションがタイムアウトします。
メインテーブルと1つの削除ステートメントの行数を次に示します。
テーブルのEventConditionsとProfileRangesには、それぞれ10億を超える行があります。
これが計画キャッシュです: https://www.brentozar.com/pastetheplan/?id=HJNg5I0B
SentryOneプランエクスプローラーを確認すると、「テーブルスプール」が2012行のProfileRangesのみをフィルターして保持しているにもかかわらず、SQLがテーブル全体を読み取っていることがわかり、EventConditionsもほぼ同じです。
Brent Ozarのsp_blitzCacheプロシージャを使用してクエリのメモリ許可を確認すると、クエリが約10GBのRAMを要求していることがわかります。
その後、クエリはSOS_SCHEDULER_YIEL(4ms後にCPUを使用する順番になるのを待つ)またはMEMORY_ALLOCATION_EXTのいずれかで待機しています。プログラムがタイムアウトして失敗します。
これを機能させるにはどうすればよいですか?
私が考えていたことの1つは、2つの最大のテーブルの外部キーを削除し、トリガーではなく行を削除することでした。しかし、私は外部キーの代わりにトリガーを使用してデータベースの整合性を強制することはあまり好きではありません。
アドバイスや助けをいただければ幸いです
ProfileRangesの主キーは
EventConditionsの主キーは
関連するすべてのテーブルに削除パスの正しいインデックスがあると仮定すると、次のことを試すことができます。
DELETE [Trips]
WHERE [ISAFileName]='ID_774199_20200311_133117.isa'
OPTION (LOOP JOIN, FAST 1, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'));
それが機能する場合は、ヒントの数を最小限にしてみてください。
これらの種類の計画は、カーディナリティの推定にとって非常に困難であり、「デフォルト」のCEモデルは、しばしば 混乱させる です。
うまくいく平面形状ができたら、必要に応じて平面ガイドなどを使ってその形状を強制できるようになります。
カスケード削除時のテーブルスキャンは、テーブルに適切なインデックスがないことの一般的な症状です。
すべてのFKテーブルに、外部キーをサポートするインデックスがあることを確認してください。 IE他のテーブルのインデックスのリーディング列としてFK列を持つクラスター化または非クラスター化インデックス。
例えば
create index ix_TripId on EventConditions (TripId)
そして、TripID FK列をクラスター化インデックスの先頭列にしないでください。
create table EventConditions
(
TripId int not null,
EventId bigint not null,
EventConditionDefId int not null,
constraint pk_EventConditions
primary key clustered(TripId, EventId, EventConditionDefId),
...
)
これにより、TripIdによるアクセスのために各テーブルが最適化されます。
さらに
私が考えていたことの1つは、2つの最大のテーブルの外部キーを削除し、トリガーではなく行を削除することでした。
FKを削除する必要はありません。最初に子テーブルから削除し、おそらくFKからON DELETE CASCADEを削除してrequire子テーブルを最初に削除します。これは、各レベルで削除されるキー値を持つ一時テーブルをロードし、上から下にロードすることから始まります。
create table #tripIdsToDelete(TripId int primary key)
insert into #tripIdsToDelete ...
create table #EventIdsToDelete(EventId int primary key)
insert into #EventIdsToDelete(EventID)
select EventId from Events
where TripId in (select TripId from #tripIdsToDelete)
...
create table #EventConditionIdsToDelete ...