web-dev-qa-db-ja.com

SQLの見積もりは、巨大なテーブルでトリガーを使用するDELETEステートメントではかなりずれています。

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つの削除ステートメントの行数を次に示します。

  • 旅行(4行)
  • セグメント(27行、SegmentIdによる旅行に関連)
  • プロファイル(SegmentIdによるセグメントに関連する2012行)
  • ProfileRanges(2337行、ProfileIdによるプロファイルに関連)
  • イベント(7750行、SegmentIdによるセグメントに関連)
  • EventConditions(9230行、EventIdによるイベントに関連)

テーブルのEventConditionsとProfileRangesには、それぞれ10億を超える行があります。

これが計画キャッシュです: https://www.brentozar.com/pastetheplan/?id=HJNg5I0B

SentryOneプランエクスプローラーを確認すると、「テーブルスプール」が2012行のProfileRangesのみをフィルターして保持しているにもかかわらず、SQLがテーブル全体を読み取っていることがわかり、EventConditionsもほぼ同じです。

ProfileRanges

TableSpool ProfileRanges

EventConditions

TableSpool EventConditions

Brent Ozarのsp_blitzCacheプロシージャを使用してクエリのメモリ許可を確認すると、クエリが約10GBのRAMを要求していることがわかります。

memory grant

その後、クエリはSOS_SCHEDULER_YIEL(4ms後にCPUを使用する順番になるのを待つ)またはMEMORY_ALLOCATION_EXTのいずれかで待機しています。プログラムがタイムアウトして失敗します。

これを機能させるにはどうすればよいですか?

私が考えていたことの1つは、2つの最大のテーブルの外部キーを削除し、トリガーではなく行を削除することでした。しかし、私は外部キーの代わりにトリガーを使用してデータベースの整合性を強制することはあまり好きではありません。

アドバイスや助けをいただければ幸いです


ProfileRangesの主キーは

  • ProfileId int
  • ProfileRangeDefId1 int
  • ProfileRangeDefId2 int

EventConditionsの主キーは

  • EventId bigint
  • EventConditionDefId int

関連するすべてのテーブルに削除パスの正しいインデックスがあると仮定すると、次のことを試すことができます。

DELETE [Trips]
WHERE [ISAFileName]='ID_774199_20200311_133117.isa'
OPTION (LOOP JOIN, FAST 1, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'));

それが機能する場合は、ヒントの数を最小限にしてみてください。

これらの種類の計画は、カーディナリティの推定にとって非常に困難であり、「デフォルト」のCEモデルは、しばしば 混乱させる です。

うまくいく平面形状ができたら、必要に応じて平面ガイドなどを使ってその形状を強制できるようになります。

17
Paul White 9

カスケード削除時のテーブルスキャンは、テーブルに適切なインデックスがないことの一般的な症状です。

すべての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 ...