web-dev-qa-db-ja.com

SQL Serverがシークプランを見つけられないのはなぜですか?これはバグですか?

create table Test1 (Id int not null, H char, primary key (Id), index i1 unique (H))
create table Test2 (Id int not null, H char, primary key (Id), index i2 unique (H))
insert into Test1 values (1, 'A'), (2, 'B')
insert into Test2 values (1, 'A'), (2, 'C')

このクエリは失敗しますQuery processor could not produce a query plan because of the hints defined in this query.(forceseekヒントを削除すると実行されますが、2つのテーブルの1つがスキャンされます-非常に大きい場合でも)

select * from Test1 a with (forceseek)
join Test2 b with (forceseek) on a.Id = b.Id
where a.H = 'A' or b.H = 'C'

この同等のクエリは正常に実行されます。

select * from Test1 a with (forceseek)
join Test2 b with (forceseek) on a.Id = b.Id
where a.H = 'A'
union
select * from Test1 a with (forceseek)
join Test2 b with (forceseek) on a.Id = b.Id
where b.H = 'C'

そして計画を与える

enter image description here

..SQL Serverが最初のクエリを最適に実行しない理由がわかりません。これは既知の問題ですか?名前はありますか?

2
gordy

問題の元のクエリについて、オプティマイザは考慮します(*) 通常、2つの主要な代替案(FORCESEEKヒントがどのテーブルでも使用されていない場合)。

最初は単純な結合です

plan 1

両方のテーブルのインデックスが(述語なしで)完全にスキャンされ、述語a.Id = b.Id AND (a.H = 'A' OR b.H = 'C')が結合ノードでテストされる場合。

2つ目はapply形式です(詳細については here

plan 2

ネストされたループ結合の外側でテーブルの1つのインデックスがスキャンされ、内側でインデックスデータが使用され、_b.Id = a.Id_ seek述語と追加の_a.H = 'A' OR b.H = 'C'_述語。 T-SQLでは次のように表すことができます。

_SELECT *
FROM Test1 a
    CROSS APPLY (
        SELECT *
        FROM Test2 b
        WHERE b.Id = a.Id AND (a.H = 'A' OR b.H = 'C')
    ) appl
_

いずれかのテーブルでFORCESEEKが使用されている場合、単純な結合の代替案は考慮から外れますが、オプティマイザは変更された形式の適用をさらに考慮します

plan 3

2つのユニオンが、他のテーブルのクラスター化インデックスにシークします。 _b.Id = a.Id_ seek述語と追加の_b.H = 'C'_述語を持つもの。そして、_b.Id = a.Id_を使用したもう1つは、_a.H = 'A'_を使用したフィルターを超えて述部をシークします。 T-SQLでは次のように表すことができます。

_SELECT *
FROM Test1 a
    CROSS APPLY (
        SELECT DISTINCT u.Id, u.H
        FROM (
            SELECT b.Id, b.H
            FROM Test2 b
            WHERE b.Id = a.Id AND a.H = 'A'
            UNION ALL
            SELECT b.Id, b.H
            FROM Test2 b
            WHERE b.Id = a.Id AND b.H = 'C'
        ) u
    ) appl
_

実際にはもっと多くの選択肢があります(たとえば、適用の内側でスプーリングを使用したり、単純な結合に別の物理結合の実装を使用したり、クラスター化インデックススキャンの代わりに非クラスター化インデックススキャンを使用したり、その逆など)。上記の実行計画の形状は非常に代表的です。

FORCESEEKが両方のテーブルで使用されている場合、新しい選択肢は表示されません。さらに、両方のテーブルでシーク要件があるため、適用後の代替案は検討後に拒否されます。

したがって、クエリの元の記述形式の可能な実装では、少なくとも1つのテーブルについてFORCESEEK要件を緩和する必要があると言えると思います。

同等のクエリがもう1つありますが、残念ながら現在のバージョンのクエリオプティマイザーでは、このような代替手段の発見は実装されていません。ただし、これはバグではなく、不完全なものです。

また、FORCESEEKを追加して、非クラスター化インデックスをシークするようオプティマイザーに説得しますが、上記の場合、オプティマイザーは独自の方法でオプティマイザーがそれを理解し、代わりにクラスター化インデックスをシークします。クエリを書き直すことは、そのパフォーマンスが満足のいくものではない場合、最初に(そして正しい)試行回数の多いものの1つです。


(*) 最終的なメモの構造と適用された変換を分析することで(ドキュメント化されていないトレースフラグ8615、8619、8621を使用して)、それを見つけることができます。

3
i-one

SQL Serverが最初のクエリを最適に実行しない理由がわかりません。これは既知の問題ですか?

SQL Serverが最初のクエリを実行していない最適ではないのではなく、まったく実行できないというだけです。 ヒント ドキュメントは言う:

  • FORCESEEKによって計画が見つからない場合、エラー8622が返されます。

そのクエリで発生した問題を理解するために、クエリの一部のクエリプランを観察してみましょう。

select * from Test1 a with (forceseek)
join Test2 b with (forceseek) on a.Id = b.Id
where a.H = 'A'

そして、ここに生成されたクエリプランがあります:

Execution Plan

SQL Serverが非クラスター化インデックスi1を最初にシークしてa.H = 'A'を見つけ、その結果、2番目のシーク(結合句on a.Id = b.Idに関連するシーク)がクラスター上で発生する可能性があることに注意してください。テーブルのインデックスTest2

where a.H = 'A' or b.H = 'C'がある場合の問題は、SQL Serverが非クラスター化インデックスi1で最初にシークしてa.H = 'A'を見つけ、その結果、結合の2番目のシークを行った場合on a.Id = b.Id、それはon a.Id = b.Id節に一致しない行を排除し、それにより、述語b.H = 'C'は正しく一致しませんでした。ジョインシークを強制。間違った結果セットを生成するリスクがないため、SQL Serverは8622エラーをスローします。

オプティマイザがインデックスユニオンプランを選択していないMartin Smith に言及されている場合、Paul Whiteは次のように述べています。

ORで区切られた複数の条件を使用した結合には、長い間問題がありました。長年にわたって、オプティマイザはそれらを同等のUNIONフォームに変換するなどの新しいトリックを追加してきましたが、利用可能な変換は制限されています。行き詰まるのは非常に簡単です。 (強調を追加)

そのため、希望する変換がリストに含まれていない可能性があり、その可能性がないと、オプションがまったくありません。 8622エラーのケース。

2
Ronaldo

最初のクエリは、「Test1とTest2のすべての行を同じIDで取得します。ここで、Test1は列に値を持っていますOR Test2は列に値を持っています」。SQLServerはwhere句のORのため、どちらの側からも行をフィルターで除外できないため、これらの両方のテーブルでシークを実行します。

2番目のクエリは、「Test1がAであるすべてのTest1-Test2ペアを、Test2がCであるすべてのTest1-Test2ペアと組み合わせてください」です。 UNIONは同等の結果を生成しますが、クエリオプティマイザーは、JOIN + ORと同じであることを確認するほど賢くないようです。

0
geofftnz