インデックスSEEKは、OPTION(RECOMPILE)でない限り使用されませんか?


11

(質問はSOから移動しました)

クラスタ化インデックス付きのテーブル(ダミーデータ)に2つの列が含まれています。

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

次に、これら2つのクエリを実行します。

declare 
@productid int =1 , 
@priceid  int = 1




SELECT productid,
       t.priceID
FROM   Transactions AS t
WHERE  (productID = @productid OR @productid IS NULL)
       AND (priceid = @priceid OR @priceid IS NULL)  


SELECT productid,
       t.priceID
FROM   Transactions AS t
WHERE  (productID = @productid)
       AND (priceid = @priceid)

両方のクエリの実際の実行プランは次のとおりです。

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

ご覧のとおり、1つ目はSCANを使用していますが、2つ目はSEEKを使用しています。

ただしOPTION (RECOMPILE)、最初のクエリに追加して、SEEKを使用するように実行プランも作成しました。

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

DBAチャットの友達は私に次のように言いました:

クエリでは、@ productid = 1です。つまり、(productID = @ productID OR @productID IS NULL)は(productID = @ productID)に簡略化できます。前者は@productIDの任意の値を処理するためにスキャンを必要とし、後者はシークを使用できます。したがって、RECOMPILEを使用すると、SQL Serverは@productIDに実際にある値を調べ、その最適な計画を作成します。@productIDにnull以外の値を指定すると、シークが最適になります。@productIDの値が不明な場合、計画は@productIDの可能な値に適合している必要があり、スキャンが必要になります。警告:OPTION(RECOMPILE)は、実行するたびにプランを強制的に再コンパイルします。これにより、すべての実行に数ミリ秒が追加されます。これは、クエリが非常に頻繁に実行される場合にのみ問題になります。

また:

@productIDがnullの場合、どの値を求めますか?回答:求めることは何もありません。すべての値が対象です。

OPTION (RECOMPILE)SQL Serverがパラメータの実際の値を確認し、それをシークできるかどうかを確認することを理解しています。

しかし、今、私は先行コンパイルの利点を失っています。

質問

IMHO-SCANは、paramがnullの場合にのみ発生します。
それは問題ありません-SQL SERVERにSCANの実行プランを作成させます。
しかし、SQL Serverがこのクエリを値を使用して何度も実行していることを検出した場合1,1、なぜ別の実行プランを作成してSEEKを使用しないのですか?

AFAIK-SQLは、最もヒットしたクエリの実行プランを作成します。

  • SQL SERVERが以下の実行プランを保存しないのはなぜですか。

    @productid int =1 , @priceid int = 1

(私はそれらの値で何度も実行します)

  • SQLにその実行プラン(SEEKを使用)を保持するように強制することは可能ですか?

完全なテーブル作成スクリプト+データ


回答:


10

チャットルームディスカッションの主なポイントのいくつかを要約します。


一般的に、SQL Serverはステートメントごとに1つのプランをキャッシュします。その計画は、可能なすべての将来のパラメータ値に対して有効でなければなりません。

クエリのシークプランをキャッシュすることはできません。たとえば、@ productidがnullの場合、そのプランは無効になるためです。

将来の一部のリリースでは、SQL Serverは、ランタイムパラメーター値に応じて、スキャンとシークのどちらかを動的に選択する単一のプランをサポートする可能性がありますが、現在のところそれはありません。

一般的な問題のクラス

クエリは、「catch all」または「dynamic search」クエリと呼ばれるパターンの例です。さまざまなソリューションがあり、それぞれに長所と短所があります。SQL Serverの最新バージョン(2008以降)では、主なオプションは次のとおりです。

  • IF ブロック
  • OPTION (RECOMPILE)
  • 動的SQLを使用 sp_executesql

このトピックに関する最も包括的な作業は、おそらくこの回答の最後にある参考文献に含まれているErland Sommarskogによるものです。関連する複雑さから逃れることはできないため、それぞれの場合のトレードオフを理解するために、それぞれのオプションを試すことに時間を費やす必要があります。

IF ブロック

IF問題の特定のケースのブロックソリューションを説明するには:

IF @productid IS NOT NULL AND @priceid IS NOT NULL
BEGIN
    SELECT 
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T
    WHERE
        T.productID = @productid
        AND T.priceID = @priceid;
END;
ELSE IF @productid IS NOT NULL
BEGIN
    SELECT 
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T
    WHERE
        T.productID = @productid;
END;
ELSE IF @priceid IS NOT NULL
BEGIN
    SELECT 
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T
    WHERE
        T.priceID = @priceid;
END;
ELSE
BEGIN
    SELECT 
        T.productID,
        T.priceID
    FROM dbo.Transactions AS T;
END;

これには、2つのパラメーター(またはローカル変数)のそれぞれについて、nullまたはnot-nullの4つの可能性がある場合の個別のステートメントが含まれているため、4つの計画があります。

パラメータスニッフィングには潜在的な問題があり、OPTIMIZE FOR各クエリでヒントが必要になる場合があります。これらの種類の微妙な点を調べるには、参照セクションを参照してください。

再コンパイル

上記の質問のように、OPTION (RECOMPILE)各呼び出しで新しい計画(シークまたはスキャン)を取得するためのヒントを追加することもできます。あなたのケースでは比較的遅い呼び出し頻度(平均で10秒ごとに1回、サブミリ秒のコンパイル時間)を考えると、このオプションはおそらくあなたに適しているようです:

SELECT
    T.productID,
    T.priceID
FROM dbo.Transactions AS T
WHERE
    (T.productID = @productid OR @productid IS NULL)
    AND (T.priceID = @priceid OR @priceid IS NULL)
OPTION (RECOMPILE);

上記のオプションの機能をクリエイティブな方法で組み合わせて、各方法の利点を最大限に活用しながら、欠点を最小限に抑えることもできます。このことを詳細に理解し、現実的なテストに裏打ちされた情報に基づく選択を行うための近道はありません。

参考文献

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