web-dev-qa-db-ja.com

宣言された結合列の順序を変更するとソートが導入されるのはなぜですか?

同じ名前、型、インデックス付きのキー列を持つ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';

これは私が欲しいクエリプランです:

This is what I want.

(警告を気にしないでください、彼らは偽の統計と関係があります。)

ただし、次のように結合で列の順序を変更すると、

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';

...これは起こります:

The query plan after changing the declared column order in the join.

Sort演算子は、宣言された結合の順序、つまりc, a, b, d, e, f, g, hに従ってストリームを並べているようです。これにより、クエリプランにブロッキング操作が追加されます。

私が見たもの

  • 列をNOT NULLに変更してみましたが、結果は同じです。
  • 元のテーブルはANSI_PADDING OFFで作成されましたが、ANSI_PADDING ONで作成してもこのプランには影響しません。
  • INNER JOINではなくLEFT JOINを試しましたが、変更はありません。
  • 私はそれを2014 SP2 Enterpriseで発見し、2017 Developer(現在のCU)で再現を作成しました。
  • 先頭のインデックス列のWHERE句を削除すると、適切な計画が生成されますが、結果にある程度影響します。

最後に、質問に行きます

  • これは意図的なものですか?
  • クエリを変更せずに並べ替えを削除できますか(これはベンダーコードなので、実際にはそうしません...)。テーブルとインデックスを変更できます。
41

これは意図的なものですか?

はい、仕様によるものです。残念ながら、マイクロソフトが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だけで多対多のマージ結合を実行する可能性が低くなり、複雑な残差が発生するという犠牲を払って、ソートを回避できることに注意してください。 。これは主に、前に述べたオプティマイザ機能間の相互作用と、トップレベルの要件が伝播する方法の説明として述べています。

30
Paul White 9

クエリを変更せずに並べ替えを削除できますか(これはベンダーコードなので、実際にはそうしません...)。テーブルとインデックスを変更できます。

インデックスを変更できる場合、#rightのインデックスの順序を変更して、結合内のフィルターの順序と一致させると、ソートが削除されます(私にとって)。

CREATE CLUSTERED INDEX IX ON #right (c, a, b, d, e, f, g, h)

驚いたことに(少なくとも私にとっては)、これはどちらのクエリも並べ替えの結果になりません。

これは意図的なものですか?

いくつかの奇妙なトレースフラグ からの出力を見ると、最終的なメモ構造に興味深い違いがあります。

screenshot of final memo structure for each query

上部の「ルートグループ」で確認できるように、両方のクエリには、このクエリを実行するための主要な物理操作としてマージ結合を使用するオプションがあります。

良いクエリ

結合なしソートは、グループ29オプション1とグループ31オプション1によって駆動されます(それぞれ、関連するインデックスの範囲スキャンです)。これは、結合をフィルター処理する一連の論理比較操作であるグループ27(図には示されていません)によってフィルター処理されます。

不正なクエリ

1つのwithソートは、これら2つのグループ(29と31)のそれぞれが持っている(新しい)オプション3によって駆動されます。オプション3は、前述の範囲スキャンの結果に対して物理的なソートを実行します(各グループのオプション1)。

どうして?

何らかの理由で、29.1と31.1をマージ結合のソースとして直接使用するオプションは、2番目のクエリのオプティマイザでも使用できません。それ以外の場合は、他のオプションの中でルートグループの下にリストされると思います。それが利用できる場合は、非常に高価なソート操作よりも確実にそれらを選択します。

私はそれを結論付けることができるだけです:

  • これは、オプティマイザの検索アルゴリズムのバグ(または、おそらく制限)です
    • インデックスと結合を5つのキーのみを持つように変更すると、2番目のクエリの並べ替えが削除されます(6、7、および8つのキーにはすべて並べ替えがあります)。
    • これは、8つのキーを持つ検索スペースが非常に大きいため、「十分なプランが見つかりました」という理由で早期に終了する前に、オプティマイザがソート以外のソリューションを実行可能なオプションとして識別する時間がないためです。
    • 結合条件の順序がオプティマイザの検索プロセスにこれほど影響を与えることは、私には少しバグがあるように見えますが、実際には少し頭がおかしいです
  • 結果の正確性を保証するためにソートが必要です
    • キーが少ない場合、またはキーが異なる順序で指定されている場合、クエリcanはソートなしで実行されるため、これはありそうにありません

うまくいけば、誰かが一緒に来て、なぜソートが必要かを説明できればいいのですが、メモの建物の違いは回答として投稿するのに十分興味深いと思いました。

20
Josh Darnell