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'
そして計画を与える
..SQL Serverが最初のクエリを最適に実行しない理由がわかりません。これは既知の問題ですか?名前はありますか?
問題の元のクエリについて、オプティマイザは考慮します(*) 通常、2つの主要な代替案(FORCESEEK
ヒントがどのテーブルでも使用されていない場合)。
最初は単純な結合です
両方のテーブルのインデックスが(述語なしで)完全にスキャンされ、述語a.Id = b.Id AND (a.H = 'A' OR b.H = 'C')
が結合ノードでテストされる場合。
2つ目はapply形式です(詳細については here )
ネストされたループ結合の外側でテーブルの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
が使用されている場合、単純な結合の代替案は考慮から外れますが、オプティマイザは変更された形式の適用をさらに考慮します
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を使用して)、それを見つけることができます。
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'
そして、ここに生成されたクエリプランがあります:
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エラーのケース。
最初のクエリは、「Test1とTest2のすべての行を同じIDで取得します。ここで、Test1は列に値を持っていますOR Test2は列に値を持っています」。SQLServerはwhere句のORのため、どちらの側からも行をフィルターで除外できないため、これらの両方のテーブルでシークを実行します。
2番目のクエリは、「Test1がAであるすべてのTest1-Test2ペアを、Test2がCであるすべてのTest1-Test2ペアと組み合わせてください」です。 UNION
は同等の結果を生成しますが、クエリオプティマイザーは、JOIN + ORと同じであることを確認するほど賢くないようです。