クラスター化された列ストアからのこの削除には、熱心なスプール演算子が役立ちますか?


28

クラスター化された列ストアインデックスからのデータの削除をテストしています。

実行計画に大きな熱心なスプールオペレーターがいることに気付きました。

ここに画像の説明を入力してください

これは、次の特性で完了します。

  • 6,000万行が削除されました
  • 1.9 GiB TempDBを使用
  • 実行時間14分
  • シリアルプラン
  • 1スプールで再バインド
  • スキャンの推定コスト:364.821

見積もりツールをだまして過小評価するようにすると、TempDBの使用を回避するより高速なプランが得られます。

ここに画像の説明を入力してください

推定スキャンコスト:56.901

(これは推定プランですが、コメントの数値は正しいものです。)

興味深いことに、次を実行してデルタストアをフラッシュすると、スプールは再び消えます。

ALTER INDEX IX_Clustered ON Fact.RecordedMetricsDetail REORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);

スプールは、デルタストアにページのしきい値を超えるしきい値がある場合にのみ導入されるようです。

デルタストアのサイズを確認するには、次のクエリを実行して、テーブルの行内ページを確認します。

SELECT  
  SUM([in_row_used_page_count]) AS in_row_used_pages,
  SUM(in_row_data_page_count) AS in_row_data_pages
FROM sys.[dm_db_partition_stats] as pstats
JOIN sys.partitions AS p
ON pstats.partition_id = p.partition_id
WHERE p.[object_id] = OBJECT_ID('Fact.RecordedMetricsDetail');

最初の計画のスプールイテレーターに妥当な利点はありますか?その存在が一貫していないため、ハロウィーンの保護ではなく、パフォーマンスの向上を目的としていると仮定する必要があります。

2016 CTP 3.1でこれをテストしていますが、2014 SP1 CU3でも同じ動作が見られます。

スキーマとデータを生成するスクリプトを投稿し、ここで問題を説明します

質問は、この時点でのオプティマイザーの動作に関する好奇心から外れています。質問を引き起こした問題(大きなスプールがいっぱいになったTempDB)の回避策があるからです。代わりにパーティションの切り替えを使用して削除しています。


2
OPTION (QUERYRULEOFF EnforceHPandAccCard)スプールを試してみると消えます。HPは「ハロウィーン保護」であると思います。しかし、その後でその計画を使用しようとしているUSE PLAN(ないからプランを使用しようとすると失敗しヒントをOPTIMIZE FOR あまりにも回避策)
マーティン・スミス

ありがとう@MartinSmith。どのようなアイデアAccCardがありますか?おそらく列のカーディナリティの昇順のカーディナリティですか?
ジェームズL

1
@JamesLupoltいいえ、私は特に納得のいくものを思い付くことができませんでした。たぶん、Accは蓄積またはアクセスですか?
マーティンスミス

回答:


22

最初の計画のスプールイテレーターに妥当な利点はありますか?

これは、あなたが「もっともらしい」と考えるものに依存しますが、コストモデルによる答えはイエスです。もちろん、オプティマイザーは常に最も安価なプランを選択するため、これは事実です。

本当の問題は、コストモデルがスプールのないプランよりもスプールのあるプランを非常に安いと考える理由です。行がデルタストアに追加される前に、(スクリプトから)新しいテーブルに対して作成された推定プランを検討します。

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE);

このプランの推定コストは、771,734単位の巨大です

元の計画

削除によって大量のランダムI / Oが発生することが予想されるため、コストはほとんどすべてクラスター化インデックスの削除に関連しています。これは、すべてのデータ変更に適用される単なる汎用ロジックです。たとえば、b-treeインデックスへの変更の順​​序付けられていないセットは、I / Oコストが高く、I / Oがほぼランダムになることを前提としています。

データ変更プランは、まさにこれらのコスト上の理由から、シーケンシャルアクセスを促進する順序で行を表示するソートを備えている場合があります。この場合、表はパーティション化されているため、影響はさらに大きくなります。実際、非常にパーティション化されています。スクリプトは15,000個を作成します。非常にパーティション化されたテーブルへのランダム更新は、途中でパーティション(行セット)を切り替える価格にも高いコストがかかるため、特に高くなります。

考慮すべき最後の主な要因は、上記の単純な更新クエリ(「更新」は削除を含むデータ変更操作を意味する)が、「行セット共有」と呼ばれる最適化の対象となることです。テーブルを更新します。実行プランには2つの別々の演算子が表示されますが、それでも、使用される行セットは1つだけです。

この最適化を適用できるということは、オプティマイザがランダムI / Oのコストを削減するために明示的に並べ替えることの潜在的な利点を考慮しないコードパスを取ることを意味するためです。テーブルがBツリーである場合、構造は本質的に順序付けられているため、これは理にかなっています。したがって、行セットを共有すると、すべての潜在的な利点が自動的に提供されます。

重要な結果は、更新演算子のコスト計算ロジックは、基になるオブジェクトが列ストアである場合、この順序付けの利点(順次I / Oまたは他の最適化の促進)を考慮しないことです。これは、列ストアの変更がインプレースで実行されないためです。デルタストアを使用します。したがって、コストモデルは、bツリーと列ストアの共有行セット更新の違いを反映しています。

それでも、(非常に!)パーティション化された列ストアの特殊なケースでは、次のパーティションに移動する前に1つのパーティションのすべての更新を実行するとI / Oの観点からも有利になる可能性があるため、順序を維持することにはメリットがあります。

ここでは列ストアに標準コストロジックが再利用されるため、パーティションの順序を保持するプラン(各パーティション内の順序ではありません)のコストは低くなります。文書化されていないトレースフラグ2332を使用して、更新演算子へのソートされた入力を要求することにより、テストクエリでこれを確認できます。これによりDMLRequestSort、更新時にプロパティがtrueに設定され、オプティマイザーは、次のパーティションに移動する前に1つのパーティションのすべての行を提供するプランを強制的に作成します。

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE, QUERYTRACEON 2332);

このプランの推定コストは非常に低く、52.5174ユニットです。

DMLRequestSort = trueプラン

このコストの削減はすべて、更新時の推定I / Oコストが低いためです。導入されたスプールは、更新で必要なパーティション順序での出力を保証できることを除いて、有用な機能を実行しませんDMLRequestSort = true(列ストアインデックスのシリアルスキャンはこの保証を提供できません)。スプール自体のコストは、特に更新時の(おそらく非現実的な)コストの削減と比較して、比較的低いと考えられています。

更新演算子への順序付き入力が必要かどうかの決定は、クエリの最適化の非常に早い段階で行われます。この決定で使用されるヒューリスティックは文書化されたことはありませんが、試行錯誤によって決定できます。デルタストアのサイズは、この決定への入力のようです。一度選択すると、選択はクエリのコンパイルに対して永続的になります。いいえUSE PLAN成功しますヒント:どちらかの計画の目標は、アップデートに入力を命じていない、またはそれはしていません。

カーディナリティの推定値を人為的に制限することなく、このクエリの低コストのプランを取得する別の方法があります。スプールを回避するための十分に低い推定値は、おそらくDMLRequestSortがfalseになり、予想されるランダムI / Oのために非常に高い推定プランコストになります。別の方法は、2332(DMLRequestSort = true)と組み合わせてトレースフラグ8649(並列プラン)を使用することです。

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE, QUERYTRACEON 2332, QUERYTRACEON 8649);

これにより、パーティションごとのバッチモードパラレルスキャンと順序を維持する(マージする)Gather Streams交換を使用する計画が作成されます。

順序付き削除

ハードウェアでのパーティションの順序付けの実行時の有効性に応じて、これは3つの中で最高のパフォーマンスを発揮します。とはいえ、大きな変更は列ストアでの素晴らしいアイデアではないため、パーティション切り替えのアイデアはほぼ確実に優れています。パーティション化されたオブジェクトでよく見られる長いコンパイル時間と風変わりなプランの選択に対処できる場合-特にパーティションの数が多い場合。

特に制限に近い、比較的新しい多くの機能を組み合わせることは、実行計画の質を下げるのに最適な方法です。オプティマイザーのサポートの深さは、時間の経過とともに向上する傾向がありますが、列ストアの15,000パーティションを使用すると、常に興味深い時代に生きることを意味します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.