あなたの実行計画
クエリプランを見ると、2つのフィルター操作を実行するために1つのインデックスが変更されていることがわかります。
簡単に言うと、TOP演算子により、行の目標が設定されました。行の目標に関するより多くの情報と前提条件はここにあります
同じソースから:
行目標戦略とは、一般に、並べ替えやハッシュなどのブロック、セットベースの操作よりも、非ブロックのナビゲーション操作(たとえば、ネストされたループ結合、インデックスシーク、ルックアップ)を優先することを意味します。これは、クライアントが迅速な起動と行の安定したストリームのメリットを享受できる場合に便利です(おそらく全体の実行時間が長くなります。上記のRob Farleyの投稿を参照してください)。たとえば、結果を一度に1ページずつ表示するなど、より明確で伝統的な使用法もあります。
テーブル全体は、行の目標が設定された左の準結合を使用してフィルターにプローブされ、5つの行をできるだけ速く効率的に返すことを期待しています。
これは発生せず、.Fulltextmatch TVFに対して多くの反復が発生します。
再作成
あなたの計画に基づいて、私はあなたの問題をいくらか再現することができました:
CREATE TABLE dbo.Person(id int not null,lastname varchar(max));
CREATE UNIQUE INDEX ui_id ON dbo.Person(id)
CREATE FULLTEXT CATALOG ft AS DEFAULT;
CREATE FULLTEXT INDEX ON dbo.Person(lastname)
KEY INDEX ui_id
WITH STOPLIST = SYSTEM;
GO
INSERT INTO dbo.Person(id,lastname)
SELECT top(12000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
REPLICATE(CAST('A' as nvarchar(max)),80000)+ CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as varchar(10))
FROM master..spt_values spt1
CROSS APPLY master..spt_values spt2;
CREATE CLUSTERED INDEX cx_Id on dbo.Person(id);
クエリを実行する
SELECT TOP (5) *
FROM dbo.Person
WHERE "id" = 1 OR contains("lastName", '"B*"');
あなたに匹敵するクエリプランへの結果:
上記の例では、Bはフルテキストインデックスに存在しません。結果として、クエリプランがどれだけ効率的になるかは、パラメーターとデータに依存します。
これについてのより良い説明は、行の目標、パート2:ポールホワイトによる準結合にあります。
...言い換えると、適用の各反復で、プッシュダウンされた結合述語を使用して、最初の一致が見つかるとすぐに入力Bの調査を停止できます。これはまさに、行の目標が適しているようなものです。最初のn個の一致する行をすばやく返すように最適化された計画の一部を生成します(ここではn = 1)。
たとえば、述部を変更して、結果がより早く検出されるようにします(スキャンの開始時)。
select top (5) *
from dbo.Person
where "id" = 124
or contains("lastName", '"A*"');
where "id" = 124
既に満たし、5行を返すフルテキストインデックスの述語による除去ますTOP()
述語。
結果もこれを示しています
id lastname
1 'AAA...'
2 'AAA...'
3 'AAA...'
4 'AAA...'
5 'AAA...'
そしてTVFの実行:
新しい行を挿入する
INSERT INTO dbo.Person
SELECT 12001, REPLICATE(CAST('B' as nvarchar(max)),80000);
INSERT INTO dbo.Person
SELECT 12002, REPLICATE(CAST('B' as nvarchar(max)),80000);
クエリを実行して以前に挿入されたこれらの行を見つける
SELECT TOP (2) *
from dbo.Person
where "id" = 1
or contains("lastName", '"B*"');
この場合も、ほとんどすべての行で繰り返しが多すぎて、最後に見つかった1つだけの値を返すことができません。
id lastname
1 'AAA...'
12001 'BBB...'
解決する
traceflag 4138を使用して行の目標を削除するとき
SELECT TOP (5) *
FROM dbo.Person
WHERE "id" = 124
OR contains("lastName", '"B*"')
OPTION(QUERYTRACEON 4138 );
オプティマイザーは、の実装に近い結合パターンを使用UNION
します。この場合、述語をそれぞれのクラスター化インデックスシークにプッシュし、行を対象とした左半結合演算子を使用しないため、これは好都合です。
上記のトレースフラグを使用せずにこれを書き込む別の方法:
SELECT top (5) *
FROM
(
SELECT *
FROM dbo.Person
WHERE "id" = 1
UNION
SELECT *
FROM dbo.Person
WHERE contains("lastName", '"B*"')
) as A;
結果のクエリプラン:
全文機能が直接適用される場所
補足として、opについては、クエリオプティマイザーの修正プログラムのトレースフラグ4199が彼の問題を解決しました。彼はこれをOPTION(QUERYTRACEON(4199))
クエリに追加することで実装しました。私は自分の側でその振る舞いを再現することができませんでした。この修正プログラムには、準結合の最適化が含まれています。
トレースフラグ:4102関数:SQL 9-クエリの実行プランに準結合演算子が含まれている場合、クエリのパフォーマンスが低下する通常、準結合演算子は、クエリにINキーワードまたはEXISTSキーワードが含まれている場合に生成されます。これを克服するには、フラグ4102および4118を有効にします。
ソース
追加
コストベースの最適化中に、オプティマイザはまたによって実装される実行計画にインデックススプール、追加することができますLogOp_Spool Index on fly Eager
(または物理的な対応を)
これは私のデータセットでこれを行いますTOP(3)
が、TOP(2)
SELECT TOP (3) *
from dbo.Physician
where "id" = 1
or contains("lastName", '"B*"')
最初の実行では、熱心なスプールが入力全体を読み取って格納してから、後で述部の実行によって要求された行のサブセットを返し、子を実行する必要なく、同じまたは異なる行のサブセットをワークテーブルから読み取って返します。再びノード。
ソース
このインデックスに熱心なスプールにシーク述語を適用すると、次のようになります。