これらのプランでは、一意のインデックスで(同じ)1000シークの推定コストが異なるのはなぜですか?


27

以下のクエリでは、両方の実行プランが一意のインデックスで1,000シークを実行すると推定されています。

シークは同じソーステーブルでの順序付けされたスキャンによって実行されるため、一見同じ順序で同じ値をシークする必要があります。

両方のネストされたループには <NestedLoops Optimized="false" WithOrderedPrefetch="true">

このタスクのコストが最初の計画では0.172434で、2番目の計画では3.01702である理由を誰もが知っていますか?

(質問の理由は、明らかにはるかに低い計画コストのために、最初のクエリが最適化として私に提案されたためです。 )

セットアップ

CREATE TABLE dbo.Target(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL);

CREATE TABLE dbo.Staging(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL); 

INSERT INTO dbo.Target
SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1,  
     master..spt_values v2;

INSERT INTO dbo.Staging
SELECT TOP (1000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1;

クエリ1 「計画の貼り付け」リンク

WITH T
     AS (SELECT *
         FROM   Target AS T
         WHERE  T.KeyCol IN (SELECT S.KeyCol
                             FROM   Staging AS S))
MERGE T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
  INSERT ( KeyCol, OtherCol )
  VALUES(S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
  UPDATE SET T.OtherCol = S.OtherCol;

クエリ2 「計画の貼り付け」リンク

MERGE Target T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
  INSERT ( KeyCol, OtherCol )
  VALUES( S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
  UPDATE SET T.OtherCol = S.OtherCol; 

クエリ1

クエリ2

上記はSQL Server 2014(SP2)(KB3171021)-12.0.5000.0(X64)でテストされました


@Joe Obbishはコメントで、より単純な再現は

SELECT *
FROM staging AS S 
  LEFT OUTER JOIN Target AS T 
    ON T.KeyCol = S.KeyCol;

SELECT *
FROM staging AS S 
  LEFT OUTER JOIN (SELECT * FROM Target) AS T 
    ON T.KeyCol = S.KeyCol;

1,000行のステージングテーブルの場合、上記の両方とも、ネストされたループを含む同じ計画形状を持ち、派生テーブルのない計画は安価に見えますが、10,000行のステージングテーブルと同じターゲットテーブルの場合、コストの違いによって計画が変更されますこのコストの不一致は、プランの比較を難しくするだけでなく、意味を持ちます(フルスキャンとマージ結合は、高価なシークよりも比較的魅力的と思われます)。

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

回答:


20

このタスクのコストが最初の計画では0.172434で、2番目の計画では3.01702である理由を誰もが知っていますか?

一般的に、ネストされたループ結合の下の内側のシークは、ランダムなI / Oパターンを想定してコストがかかります。後続のアクセスには単純な置換ベースの削減があり、以前の反復によって必要なページがすでにメモリに取り込まれている可能性を考慮しています。この基本的な評価により、標準(より高い)コストが生成されます。

もう1つの原価計算入力であるSmart Seek Costingがありますが、詳細はほとんどわかっていません。私の推測(そして、それがこの段階にあるのはそれだけです)は、おそらくローカルの順序や取得する値の範囲を考慮することによって、SSCが内側のシークI / Oコストをより詳細に評価しようとすることです。知るか。

たとえば、最初のシーク操作では、要求された行だけでなく、そのページのすべての行が(インデックス順に)取り込まれます。全体的なアクセスパターンを考えると、先読みとプリフェッチが無効になっていても、1000シークで1000行をフェッチするのに必要な物理読み取りは2つだけです。その観点から、デフォルトのI / Oコストは大幅に過大評価されており、SSC調整後のコストはより現実に近いものです。

ループがインデックスシークを多かれ少なかれ直接駆動し、結合外部参照がシーク操作の基礎となる場合、SSCが最も効果的であると期待するのは合理的と思われます。私が言えることから、SSCは常に適切な物理操作を試みますが、ほとんどの場合、他の操作によってシークが結合から分離されると、下方調整は行われません。単純なフィルターは、これの1つの例外です。おそらく、SQL Serverがこれらをデータアクセス演算子にプッシュすることが多いためです。いずれにせよ、オプティマイザーは選択をかなり深くサポートしています。

ここで、サブクエリの外部投影のCompute ScalarがSSCに干渉しているように見えるのは残念です。通常、計算スカラーは結合の上に再配置されますが、これらは現在の場所にとどまる必要があります。それでも、ほとんどの通常の計算スカラーは最適化に対して非常に透過的であるため、これは少し驚くべきことです。

いずれにせよ、物理操作PhyOp_Rangeがインデックスの単純な選択から生成される場合SelIdxToRng、SSCは効果的です。より複雑なSelToIdxStrategy(インデックス戦略に対するテーブルの選択)が採用されると、結果PhyOp_RangeはSSCを実行しますが、結果は減少しません。繰り返しになりますが、SSCでは、より単純で直接的な操作が最適に機能するようです。

SSCの機能を正確に伝え、正確な計算を表示できるようにしたいのですが、詳細はわかりません。自分で利用できる限られたトレース出力を調べたい場合は、文書化されていないトレースフラグ2398を使用できます。出力例は次のとおりです。

スマートシークコスト(7.1):: 1.34078e + 154、0.001

その例は、メモグループ7、代替1、コストの上限、および係数0.001を示しています。よりクリーンな要因を確認するには、ページができるだけ密になるように、並列処理を行わずにテーブルを再構築してください。これを行わないと、例のターゲットテーブルの係数は0.000821のようになります。もちろん、そこにはかなり明らかな関係があります。

SSCは、文書化されていないトレースフラグ2399を使用して無効にすることもできます。そのフラグをアクティブにすると、両方のコストが高い値になります。


8

これが答えかどうかは定かではありませんが、コメントには少し長めです。違いの原因は、私の側の純粋な憶測であり、おそらく他の人の思考の糧になる可能性があります。

実行プランを使用したクエリの簡素化。

SELECT S.KeyCol, 
       S.OtherCol,
       T.*
FROM staging AS S 
  LEFT OUTER JOIN Target AS T 
    ON T.KeyCol = S.KeyCol;

SELECT S.KeyCol, 
       S.OtherCol,
       T.*
FROM staging AS S 
  LEFT OUTER JOIN (
                  SELECT *
                  FROM Target
                  ) AS T 
    ON T.KeyCol = S.KeyCol;

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

実際に同一の実行プランをもたらす可能性のあるこれらの同等のクエリ間の主な違いは、計算スカラー演算子です。なぜそこにある必要があるのか​​わかりませんが、それは、オプティマイザーが派生テーブルを最適化するためにできる限りだと思います。

私の推測では、計算スカラーの存在は、2番目のクエリのIOコストを台無しにしているものです。

オプティマイザーの内部から:計画原価計算

CPUコストは、最初の行では0.0001581、後続の行では0.000011として計算されます。
...
0.003125のI / Oコストは正確に1/320 です。これは、ディスクサブシステムが1秒あたり320のランダムI / O操作を実行できるというモデルの仮定を反映してい
ます...
コスト計算コンポーネントは、ディスクから取り込む必要があるページは、テーブル全体を格納するのに必要なページ数を超えることはできません。

私の場合、テーブルは5618ページかかり、1000000行から1000行を取得するために必要な推定ページ数は5.618で、IOコストは0.015625になります。

両方のクエリのCPUコストは同じになり0.0001581 * 1000 executions = 0.1581ます。

上記のリンクの記事によると、最初のクエリのコストは0.173725であると計算できます。

そして、計算スカラーがIOコストの混乱をどのように作っているのかが正しいと仮定すると、3.2831に計算できます。

計画に示されているとおりではありませんが、近隣にあります。


6

(これはPaulの答えへのコメントとしては良いでしょうが、まだ十分な担当者がいません。)

DBCC将来的に同様の不一致を調査するのに役立つ場合に備えて、私はかつて結論に近かったトレースフラグのリスト(およびいくつかのステートメント)を提供したかったのです。これらのすべてを本番環境で使用しないでください。

最初に、最終メモを見て、どの物理演算子が使用されているかを確認しました。グラフィカルな実行計画によれば、それらは確かに同じように見えます。そこで、トレースフラグ3604とを使用しまし8615た。最初は出力をクライアントに送信し、2番目は最終メモを表示します。

SELECT S.*, T.KeyCol
FROM Staging AS S
      LEFT OUTER JOIN Target AS T
       ON T.KeyCol = S.KeyCol
OPTION(QUERYTRACEON 3604, -- Output client info
       QUERYTRACEON 8615, -- Shows Final Memo structure
       RECOMPILE);

からさかのぼって、Root Groupこれらのほぼ同一のPhyOp_Range演算子を見つけました。

  1. PhyOp_Range 1 ASC 2.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 0.175559(Distance = 2)
  2. PhyOp_Range 1 ASC 3.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 3.01702(Distance = 2)

私にとって唯一の明らかな違いは2.03.0、それぞれの「メモグループ2、オリジナル」と「メモグループ3、オリジナル」を指します。メモをチェックすると、これらは同じものを指しているので、違いはまだ明らかにされていません。

第二に、私には無益であることが証明されたトレースフラグの混乱を調べましたが、興味深い内容がいくつかあります。私はベンジャミンネバレスからほとんど持ち上げた。ある場合に適用され、他の場合には適用されなかった最適化ルールに関する手がかりを探していました。

 SELECT S.*, T.KeyCol
 FROM Staging AS S
      LEFT OUTER JOIN Target AS T
        ON T.KeyCol = S.KeyCol
 OPTION (QUERYTRACEON 3604, -- Output info to client
         QUERYTRACEON 2363, -- Show stats and cardinality info
         QUERYTRACEON 8675, -- Show optimization process info
         QUERYTRACEON 8606, -- Show logical query trees
         QUERYTRACEON 8607, -- Show physical query tree
         QUERYTRACEON 2372, -- Show memory utilization info for optimization stages 
         QUERYTRACEON 2373, -- Show memory utilization info for applying rules
         RECOMPILE );

第三に、PhyOp_Range似ているように見えるルールがどのルールに適用されているかを見ました。ポールがブログ投稿で言及した2つのトレースフラグを使用しました。

SELECT S.*, T.KeyCol
FROM Staging AS S
      LEFT OUTER JOIN (SELECT KeyCol
                      FROM Target) AS T
       ON T.KeyCol = S.KeyCol
OPTION (QUERYTRACEON 3604, -- Output info to client
        QUERYTRACEON 8619, -- Show applied optimization rules
        QUERYTRACEON 8620, -- Show rule-to-memo info
        QUERYTRACEON 8621, -- Show resulting tree
        QUERYTRACEON 2398, -- Show "smart seek costing"
        RECOMPILE );

出力から、演算子JOINを取得するためにこのルールが直接適用されたことがわかりPhyOp_RangeますRule Result: group=7 2 <SelIdxToRng>PhyOp_Range 1 ASC 2 (Distance = 2)。代わりに、副選択がこのルールを適用しました:Rule Result: group=9 2 <SelToIdxStrategy>PhyOp_Range 1 ASC 3 (Distance = 2)。ここには、各ルールに関連付けられた「スマートシークコスト」情報も表示されます。直接の場合、JOINこれは出力です(私にとって)Smart seek costing (7.2) :: 1.34078e+154 , 0.001。副選択の場合、これは出力です。Smart seek costing (9.2) :: 1.34078e+154 , 1

最後に、私は多くを結論付けることができませんでした-しかし、ポールの答えはギャップのほとんどを閉じます。スマートシークのコストに関する詳細情報をご覧ください。


4

これも実際には答えではありません-ミカエルが指摘したように、コメントでこの問題を議論することは困難です...

興味深いことに、サブクエリ(select KeyCol FROM Target)をインラインTVFに変換すると、プランとそのコストは単純な元のクエリと同じになります。

CREATE FUNCTION dbo.cs_test()
RETURNS TABLE
WITH SCHEMABINDING
AS 
RETURN (
    SELECT KeyCol FROM dbo.Target
    );

/* "normal" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN Target AS T ON T.KeyCol = S.KeyCol;

/* "subquery" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN (SELECT KeyCol FROM Target) AS T ON T.KeyCol = S.KeyCol;

/* "inline-TVF" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN dbo.cs_test() t ON s.KeyCol = t.Keycol

クエリプラン(pastetheplanリンク):

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

推論は、原価計算エンジンが、このタイプのサブクエリが持つ可能性のある影響について混乱していると思うように導きます。

たとえば、次のことを考えてください。

SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN (
        SELECT KeyCol = CHECKSUM(NEWID()) 
        FROM Target
        ) AS T ON T.KeyCol = S.KeyCol;

どうだろう、あなたはそれをコスト?クエリオプティマイザーは、上記の「サブクエリ」バリアントと非常によく似たプランを選択し、計算スカラーを含んでいます(pastetheplan.comリンク):

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

計算スカラーのコストは上記の「サブクエリ」バリアントとはまったく異なりますが、クエリオプティマイザーには、返される行の数を事前に知る方法がないため、単なる推測にすぎません。行の見積もりは不明であり、したがってターゲットテーブルの行数に設定されるため、プランは左外部結合にハッシュ一致を使用します。

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

ミカエルが彼の答えで行った仕事に同意することを除いて、これから大きな結論はありません。

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