最初の計画のスプールイテレーターに妥当な利点はありますか?
これは、あなたが「もっともらしい」と考えるものに依存しますが、コストモデルによる答えはイエスです。もちろん、オプティマイザーは常に最も安価なプランを選択するため、これは事実です。
本当の問題は、コストモデルがスプールのないプランよりもスプールのあるプランを非常に安いと考える理由です。行がデルタストアに追加される前に、(スクリプトから)新しいテーブルに対して作成された推定プランを検討します。
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ユニットです。
このコストの削減はすべて、更新時の推定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パーティションを使用すると、常に興味深い時代に生きることを意味します。
OPTION (QUERYRULEOFF EnforceHPandAccCard)
スプールを試してみると消えます。HPは「ハロウィーン保護」であると思います。しかし、その後でその計画を使用しようとしているUSE PLAN
(ないからプランを使用しようとすると失敗しヒントをOPTIMIZE FOR
あまりにも回避策)