次のクエリは、12kレコードのテーブルで完了するまでに約10秒かかります
select top (5) *
from "Physician"
where "id" = 1 or contains("lastName", '"a*"')
しかし、where句を次のいずれかに変更した場合
where "id" = 1
または
where contains("lastName", '"a*"')
すぐに戻ります。
両方の列にインデックスが付けられ、lastName列にもフルテキストインデックスが付けられます。
CREATE TABLE Physician
(
id int identity NOT NULL,
firstName nvarchar(100) NOT NULL,
lastName nvarchar(100) NOT NULL
);
ALTER TABLE Physician
ADD CONSTRAINT Physician_PK
PRIMARY KEY CLUSTERED (id);
CREATE NONCLUSTERED INDEX Physician_IX2
ON Physician (firstName ASC);
CREATE NONCLUSTERED INDEX Physician_IX3
ON Physician (lastName ASC);
CREATE FULLTEXT INDEX
ON "Physician" ("firstName" LANGUAGE 0x0, "lastName" LANGUAGE 0x0)
KEY INDEX "Physician_PK"
ON "the_catalog"
WITH stoplist = off;
これが 実行計画 です。
何が問題でしょうか?
クエリプランを見ると、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*"');
_
フルテキストインデックス述語が既に5行を返し、TOP()
述語を満たすため、_where "id" = 124
_は削除されます。
結果もこれを示しています
_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*"')
_
最初の実行では、熱心なスプールが入力全体を読み取って格納してから、後で述部の実行によって要求された行のサブセットを返し、子を実行する必要なく、同じまたは異なる行のサブセットをワークテーブルから読み取って返します。再びノード。
このインデックスに熱心なスプールにシーク述語を適用すると、次のようになります。