where句で 'contains'と '='を一緒に使用するとクエリが遅くなる


8

次のクエリは、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;

ここに実行計画があります

何が問題でしょうか?


テーブルの定義を追加しました
Hooman Valibeigi

回答:


11

あなたの実行計画

クエリプランを見ると、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*"')  

ここに画像の説明を入力してください

最初の実行では、熱心なスプールが入力全体を読み取って格納してから、後で述部の実行によって要求された行のサブセットを返し、子を実行する必要なく、同じまたは異なる行のサブセットをワークテーブルから読み取って返します。再びノード。

ソース

このインデックスに熱心なスプールにシーク述語を適用すると、次のようになります。

ここに画像の説明を入力してください


トレースフラグの使用について説明できますか?あなたのコードから彼らが何をしているかは少し不明瞭です
George.Palacios

1
@ George.Palaciosええ、私はそれを少し混乱させました:^)。フィードバックに感謝します!
Randi Vertongen

提案されたQUERYTRACEONフラグ(4138、3604、8607、8612)は機能しませんでしたが、QUERYTRACEON 4199が問題を解決しました!!!!
Hooman Valibeigi

TOP演算子がなくてもクエリが遅いことに注意してください
Hooman Valibeigi

@HoomanValibeigi下部にあるユニオンソリューションを試しましたか?
Randi Vertongen
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.