以下のクエリでは、両方の実行プランが一意のインデックスで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;
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;
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;
上記は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行のステージングテーブルの場合、上記の両方のループはネストされた同じプラン形状であり、派生テーブルのないプランappearingの方が安価ですが、 10,000行のステージングテーブルと上記と同じターゲットテーブルでは、コストの違いによりプランの形状が変わります(フルスキャンとマージ結合は、コストのかかるシークよりも比較的魅力的であるように見えます)。計画を比較します。
このタスクのコストが最初のプランでは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がこれらをデータアクセスオペレーターにプッシュできることが多いためです。いずれにしても、オプティマイザは選択をかなり深くサポートしています。
残念ながら、サブクエリの外部投影の計算スカラーが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に近くなります。もちろん、そこにはかなり明白な関係がいくつかあります。
文書化されていないトレースフラグ2399を使用してSSCを無効にすることもできます。そのフラグをアクティブにすると、どちらのコストも高い値になります。
答えはわかりませんが、コメントには少し時間がかかります。違いの原因は、私の側の純粋な推測であり、おそらく他の人に考えるための食べ物かもしれません。
実行プランを使用した簡素化されたクエリ。
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コストを押し上げています。
From オプティマイザ内部:計画原価計算
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 Costの計算を混乱させている方法について私が正しいと仮定すると、3.2831と計算できます。
計画に示されているものとは正確には異なりますが、それは近所にあります。
(これはPaulの回答へのコメントとしては優れていますが、まだ十分な担当者がいません。)
トレースフラグ(およびDBCC
ステートメント)のリストを提供したかったのですが、将来同様の不一致を調査するのに役立つ場合に備えて、ほぼ結論に達していました。これらはすべて、プロダクションでは使用しないでください。
まず、 Final Memo を見て、使用されている物理演算子を確認しました。グラフィカルな実行計画によれば、それらは確かに同じに見えます。そこで、トレースフラグ_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
_演算子が見つかりました。
PhyOp_Range 1 ASC 2.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 0.175559(Distance = 2)
PhyOp_Range 1 ASC 3.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 3.01702(Distance = 2)
私にとって明らかな違いは、_2.0
_と_3.0
_だけで、それぞれ「メモグループ2オリジナル」と「メモグループ3オリジナル」を指します。メモを確認すると、これらは同じものを指しているため、まだ違いは明らかにされていません。
次に、私には無益であることが判明したトレースフラグの全体を調べましたが、興味深い内容が含まれています。私は Benjamin Nevarez から最も持ち上げました。あるケースで適用され、他のケースでは適用されない最適化ルールについての手がかりを探していました。
_ 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 );
_
3番目に、似ている_PhyOp_Range
_ sに適用されているルールを確認しました。 ブログ投稿 でPaulが言及したトレースフラグをいくつか使用しました。
_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 );
_
出力から、direct -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)
。ここには、各ルールに関連付けられた「スマートシークコスト」情報が表示されます。 direct -JOIN
の場合、これは(私にとって)出力です:Smart seek costing (7.2) :: 1.34078e+154 , 0.001
。副選択の場合、これは出力です:Smart seek costing (9.2) :: 1.34078e+154 , 1
。
結局、私はあまり結論を出すことができませんでした-しかし、ポールの答えはほとんどのギャップを埋めます。スマートシークの原価計算について、もう少し詳しく知りたいのですが。
これも実際には答えではありません-ミカエルが指摘したように、コメントでこの問題を議論することは困難です...
興味深いことに、サブクエリを変換すると(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 link ):
控除により、原価計算エンジンは このタイプのサブクエリがもたらす可能性のある影響 について混乱していると思います。
たとえば、次のようにします。
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 link )を含む、上記の「サブクエリ」バリアントと非常に類似したプランを選択します。
計算スカラーのコストは、上記の「サブクエリ」バリアントとはかなり異なりますが、クエリオプティマイザーには、返される行数がアプリオリに何であるかを事前に知る方法がないため、これは単なる推測です。行の推定値は認識できないため、ターゲットテーブルの行数に設定されるため、プランは左外部結合にハッシュ一致を使用します。
ミカエルが彼の答えでした仕事に同意することを除いて、私はこれからの素晴らしい結論を持っていません、そして他の誰かがより良い答えを考え出すことができることを望んでいます。