使用可能なすべての列を使用していない述語を探す


8

再現が難しい奇妙なクエリコンパイルの問題があります。これは高負荷でのみ発生し、簡単に繰り返すことはできません。

  • 列A、B、C、Dを持つテーブルTがあります。
  • T(A、B、C、D)に一意でないクラスター化インデックスがあります。
  • クエリSELECT * FROM T WHERE A = @ P1 AND B = @ P2 AND(C = @ P3 OR C = @ P4)AND D = @ P5があります。シーク条件はクラスター化インデックスのすべての列にあり、3番目の列にはORがあります。

問題は、このクエリのクエリプランにはAとBのみにシーク述語があることです。CおよびDの述語は通常の述語であるため、列CおよびDの検索ツリーは使用されません。

すべてのパラメーターのデータ型は、列のデータ型と一致します。

なぜこれが起こり得るのかについてのヒントを誰かが提供できますか?SQLバージョンは2008 R2(SP1)-10.50.2789.0(X64)です


実際に、4つの列すべてで等価シークを実行するパラメーター化されたクエリの計画を取得しましたか?もしそうなら、あなたは使っていOPTION (RECOMPILE)ますか?
マーティン・スミス

回答:


8

パラメータ化されたクエリの場合、2つのシークを実行することはできません

WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5 

そして

WHERE A=@P1 AND B=@P2 AND C=@P4 AND D=@P5 

なぜなら、@P3 = @P4それが重複した行を誤って戻すからです。したがって、最初に重複を削除する演算子が必要になります。

簡単なテストから、この目的は、それが得られるかどうかに関係なく、テーブルのサイズに依存しているように見えます。以下のテストでは、245/ 246行はプラン間のカットオフポイントです(これは、1ページにすべてが一致するインデックスと2つのリーフページとルートページになるインデックス間のカットオフポイントでもあります)。

CREATE TABLE T(A INT,B INT,C INT,D INT)

INSERT INTO T
SELECT TOP (245) 1,2,3,5
FROM master..spt_values v1

CREATE CLUSTERED INDEX IX ON T(A, B, C, D)

SELECT index_level,page_count, record_count
FROM sys.dm_db_index_physical_stats(db_id(),object_id('T'),1,NULL, 'DETAILED')

DECLARE @C1 INT = 3,
        @C2 INT = 4

 SELECT * FROM T WHERE A=1 AND B=2 AND (C=@C1 OR C=@C2) AND D=5

 DROP TABLE T

1ページ/ 245行

この計画は、A=1 AND B=2残余述語でシークしています(C=@C1 OR C=@C2) AND D=5

計画1

2リーフページ/ 246行

計画2

2番目の計画では、追加のオペレーターは@C1,@C2、シークを実行する前に最初から重複を削除する責任があります。

実際範囲の間シークされる第二平面にシークA=1 AND B=2 AND C > Expr1010し、A=1 AND B=2 AND C < Expr1011上の残留述語とD=5。それでも、4つの列すべてに対する平等シークではありません。追加の計画演算子の詳細については、こちらをご覧ください

追加OPTION (RECOMPILE)すると、コンパイル時に重複するパラメータ値を検査して、2つの等価シークを含む計画を作成できます。

あなたもそれを達成することができます

;WITH CTE
     AS (SELECT DISTINCT ( C )
         FROM   (VALUES (@C1),
                        (@C2)) V(C))
SELECT CA.*
FROM   CTE
       CROSS APPLY (SELECT *
                    FROM   T
                    WHERE A=1 AND B=2 AND D=5  AND C = CTE.C) CA

計画3

ただし、実際にはこのテストケースでは、1つではなく2つのシークを1つのページインデックスに求めることで論理IOが増加するため、逆効果になる可能性があります。


1
あなたが最初のコメントを追加する前の昨夜、この質問についていくつかのテストをしました。私はその行動を見たところまで来ましたが、それを引き起こしている原因がわからなかったので(@ P3 = @ P4)、そのため+1しました(すでにそうしました)。私select .. union select ...はまた、2つのシークと、結果から重複を削除する追加のステップを提供すると思います。
ミカエルエリクソン2013年

1
@MikaelEriksson-ただしSELECT * FROM T WHERE A=1 AND B=2 AND C=@C1 AND D=5 UNION SELECT * FROM T WHERE A=1 AND B=2 AND C=@C2 AND D=5、返される重複を誤って削除する可能性があります。私がすべて同じ値を遅延的に入力した私のサンプルデータでは、1行ではなく返されます256
Martin Smith

2
ああ、そうです。OPは、キーが一意でないことも指定しました。ラッキー私はこれに答えようとしませんでした:)。
ミカエルエリクソン2013年

4

マーティンの分析に完全に同意します。このクエリでは、OPTION(RECOMPILE)が(たぶん)ない限り、OR述語のため、4つの列すべてでシークを生成できません。私はこれが干し草の針のようなクエリであると思いますが、おそらく余分なオーバーヘッドは必要ありません。

これはどう:

IF @P3=@P4
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5.
ELSE
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5.
    UNION ALL
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P4 AND D=@P5.

私はこれをテストしませんでしたが、else部分は4つの値すべてに対して2つのシークを提供し、連結によって安価なユニオンを提供します。両方のシークが同じページで終了する場合、余分な論理ページの読み取りが多くの時間を追加するとは思いません。ただし、データによっては、2つのシークが別のページにある場合があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.