同じ名前、型、インデックス付きのキー列を持つ2つのテーブルがあります。それらの1つは一意クラスター化インデックスを持ち、もう1つは非一意を持ちます。
テスト設定
いくつかの現実的な統計を含む設定スクリプト:
DROP TABLE IF EXISTS #left;
DROP TABLE IF EXISTS #right;
CREATE TABLE #left (
a char(4) NOT NULL,
b char(2) NOT NULL,
c varchar(13) NOT NULL,
d bit NOT NULL,
e char(4) NOT NULL,
f char(25) NULL,
g char(25) NOT NULL,
h char(25) NULL
--- and a few other columns
);
CREATE UNIQUE CLUSTERED INDEX IX ON #left (a, b, c, d, e, f, g, h)
UPDATE STATISTICS #left WITH ROWCOUNT=63800000, PAGECOUNT=186000;
CREATE TABLE #right (
a char(4) NOT NULL,
b char(2) NOT NULL,
c varchar(13) NOT NULL,
d bit NOT NULL,
e char(4) NOT NULL,
f char(25) NULL,
g char(25) NOT NULL,
h char(25) NULL
--- and a few other columns
);
CREATE CLUSTERED INDEX IX ON #right (a, b, c, d, e, f, g, h)
UPDATE STATISTICS #right WITH ROWCOUNT=55700000, PAGECOUNT=128000;
再現
これらの2つのテーブルをクラスタリングキーで結合すると、次のように1対多のMERGE結合が期待されます。
SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
l.a=r.a AND
l.b=r.b AND
l.c=r.c AND
l.d=r.d AND
l.e=r.e AND
l.f=r.f AND
l.g=r.g AND
l.h=r.h
WHERE l.a='2018';
これは私が欲しいクエリプランです:
(警告を気にしないでください、彼らは偽の統計と関係があります。)
ただし、次のように結合で列の順序を変更すると、
SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
l.c=r.c AND -- used to be third
l.a=r.a AND -- used to be first
l.b=r.b AND -- used to be second
l.d=r.d AND
l.e=r.e AND
l.f=r.f AND
l.g=r.g AND
l.h=r.h
WHERE l.a='2018';
...これは起こります:
Sort演算子は、宣言された結合の順序、つまりc, a, b, d, e, f, g, h
に従ってストリームを並べているようです。これにより、クエリプランにブロッキング操作が追加されます。
私が見たもの
NOT NULL
に変更してみましたが、結果は同じです。ANSI_PADDING OFF
で作成されましたが、ANSI_PADDING ON
で作成してもこのプランには影響しません。INNER JOIN
ではなくLEFT JOIN
を試しましたが、変更はありません。最後に、質問に行きます
これは意図的なものですか?
はい、仕様によるものです。残念ながら、マイクロソフトがConnectフィードバックサイトを引退させたため、SQL Serverチームの開発者からの多くの有用なコメントが消滅し、このアサーションの最良の公開ソースが失われました。
とにかく、現在のオプティマイザの設計では、不要なソートを回避するために積極的にシークしませんそれ自体。これは、ウィンドウ関数などで最も頻繁に発生しますが、順序付け、特に演算子間の保存された順序付けに敏感な他の演算子でも見られます。
それでも、オプティマイザは不要な並べ替えを回避するのに(多くの場合)非常に優れていますが、この結果は通常、異なる順序の組み合わせを積極的に試みる以外の理由で発生します。その意味では、許容可能なコストで一般的な計画の品質を向上させることが示されている直交オプティマイザ機能間の複雑な相互作用の問題であるため、「検索スペース」の問題ではありません。
たとえば、並べ替えは、順序付け要件(トップレベルの_ORDER BY
_など)を既存のインデックスに一致させるだけで回避できることがよくあります。当然のことながら、これは_ORDER BY l.a, l.b, l.c, l.d, l.e, l.f, l.g, l.h;
_を追加することを意味する場合がありますが、これは単純化しすぎます(クエリを変更したくないため、受け入れられません)。
より一般的には、各メモグループは、入力順序を含む、必要なプロパティまたは望ましいプロパティに関連付けることができます。特定の順序をenforceする明確な理由がない場合(たとえば、_ORDER BY
_を満たすため、または順序に依存することから正しい結果を保証するため)物理的なオペレーター)、「運」の要素が含まれています。 マージ結合連結によるソートの回避 で、マージ結合(ユニオンモードまたは結合モード)に関連する詳細を記述しました。その多くは、サポートされている製品の表面積を超えているため、情報として扱い、変更される可能性があります。
あなたの特定のケースでは、はい、あなたはインデックスを調整することができます jadarnel27が示唆するように ソートを避けるために;ここで実際にマージ結合を好む理由はほとんどありません。データの知識、および最良、最悪、平均のトレードオフに応じて、クエリを変更せずにプランガイドを使用して、OPTION(HASH JOIN, LOOP JOIN)
でハッシュまたはループ物理結合を選択するヒントを与えることもできますケース性能。
最後に、好奇心として、単純な_ORDER BY l.b
_を使用すると、b
だけで多対多のマージ結合を実行する可能性が低くなり、複雑な残差が発生するという犠牲を払って、ソートを回避できることに注意してください。 。これは主に、前に述べたオプティマイザ機能間の相互作用と、トップレベルの要件が伝播する方法の説明として述べています。
クエリを変更せずに並べ替えを削除できますか(これはベンダーコードなので、実際にはそうしません...)。テーブルとインデックスを変更できます。
インデックスを変更できる場合、#right
のインデックスの順序を変更して、結合内のフィルターの順序と一致させると、ソートが削除されます(私にとって)。
CREATE CLUSTERED INDEX IX ON #right (c, a, b, d, e, f, g, h)
驚いたことに(少なくとも私にとっては)、これはどちらのクエリも並べ替えの結果になりません。
これは意図的なものですか?
いくつかの奇妙なトレースフラグ からの出力を見ると、最終的なメモ構造に興味深い違いがあります。
上部の「ルートグループ」で確認できるように、両方のクエリには、このクエリを実行するための主要な物理操作としてマージ結合を使用するオプションがあります。
結合なしソートは、グループ29オプション1とグループ31オプション1によって駆動されます(それぞれ、関連するインデックスの範囲スキャンです)。これは、結合をフィルター処理する一連の論理比較操作であるグループ27(図には示されていません)によってフィルター処理されます。
1つのwithソートは、これら2つのグループ(29と31)のそれぞれが持っている(新しい)オプション3によって駆動されます。オプション3は、前述の範囲スキャンの結果に対して物理的なソートを実行します(各グループのオプション1)。
何らかの理由で、29.1と31.1をマージ結合のソースとして直接使用するオプションは、2番目のクエリのオプティマイザでも使用できません。それ以外の場合は、他のオプションの中でルートグループの下にリストされると思います。それが利用できる場合は、非常に高価なソート操作よりも確実にそれらを選択します。
私はそれを結論付けることができるだけです:
うまくいけば、誰かが一緒に来て、なぜソートが必要かを説明できればいいのですが、メモの建物の違いは回答として投稿するのに十分興味深いと思いました。