web-dev-qa-db-ja.com

このストアドプロシージャでクラスター化インデックススキャンが発生するのはなぜですか?

私は私の研究に基づいて答えを知っていると思いますが、エンジンがどのように/なぜプランをコンパイルするのかについての確認を探しています

渡されるパラメーター:@ID int、@ OtherID INT

_SELECT  b.Column1
        ,b.Column2
        ,b.Column3
        ,b.Column4
        ,b.Column5
        ,c.Column1
        ,b.Column1
        ,e.Column1
FROM    Table1 AS b 
        inner join Table2 AS t
        on b.ID = t.ID
        left join [LINKED SERVER].[DB].dbo.Table3 as c
        on b.ID = c.ID
        left join Table4 AS e
        on b.ID= e.ID
where   (b.ID = @ID or @ID= 0)
        And b.ID = @OtherID
        And b.ID IS NOT NULL
        and e.ID = 1
_

これで、インデックススキャンの原因は次の行にあると判断しました:where (b.ID = @ID or @ID= 0)。より具体的には、@ ID =0。さらに明確にするために、そのIDフィールドの0が基になるテーブルに値として存在しない場合、これは単に、開発者が渡すことですべての結果をプルバックできるようにするものでした。パラメータに0を設定し、そのパラメータが0かどうかを確認して、結果としてより多くの行がプルバックされるようにします(通常、1〜3の結果を返すだけです)。

さて、非常に奇妙なのは、_OPTION RECOMPILE_を追加すると、エンジンはオーバーヘッド(コンパイル時間)を犠牲にして、はるかに優れたプランを作成できることです。

enter image description here

私が知りたいのは、これがどのようにして可能かということです。私がオンラインで読んだものから、_OPTION RECOMPILE_を使用することにより、エンジンは文字通り値をパラメーターに渡された実際の値に置き換え、@ ID 1234が0に等しくないことを非常に簡単に確認できます。ただし、 _OPTION RECOMPILE_を使用しないエンジンは、合計レコード数(120,000)を取得し、それを個別の可能性の総数(107,000)で割ります。これは約1.1の推定行が返されることになり、インデックススキャンを含むプランの推定プロパティを確認することでこれを確認しましたが、推定が正しい場合、エンジンはインデックススキャンを続行するのはなぜですか?念のため統計を更新しました。

enter image description here

1
Dylan Janszen
_b.ID = @ID OR @ID = 0
_

プランはキャッシュされて再利用されるため、オプティマイザはインデックススキャンを使用してプランを生成する必要があります。

その後の実行では、パラメーター_@ID_がゼロになる可能性があります。その場合、シークするID値がないため、インデックスシークには値がありません。または、_@ID_にゼロ以外の値が指定されることもありますが、キャッシュされたプランは、すべての可能なパラメーター値に対して正しく機能する必要があります。

OPTION (RECOMPILE)を使用する場合、Parameter Embedding Optimization(PEO)は、各実行で_@ID_の現在の値がパラメーターの代わりに使用されることを意味します、およびプランはキャッシュされません。

_@ID_が1234であるとします。PEOの後、オプティマイザは次のように認識します。

_b.ID = 1234 OR 1234 = 0
_

これは、矛盾検出ロジックによって次のように簡素化されます。

_b.ID = 1234
_

...IDのシークを有効にします。

詳細については、私の記事 パラメータのスニッフィング、埋め込み、およびRECOMPILEオプション を参照してください。

2
Paul White 9

問題はオプションの条件_(b.ID = @ID or @ID= 0)_です

OPTION(RECOMPILE)を使用したくない場合は、次の条件でクエリを分割する必要があります。

_IF @ID = 0 BEGIN
    SELECT b.Column1, 
           b.Column2, 
           b.Column3, 
           b.Column4, 
           b.Column5, 
           c.Column1, 
           b.Column1, 
           e.Column1
    FROM Table1 AS b
         INNER JOIN Table2 AS t ON b.ID = t.ID
         LEFT JOIN [LINKED SERVER].[DB].dbo.Table3 AS c ON b.ID = c.ID
         LEFT JOIN Table4 AS e ON b.ID = e.ID
    WHERE b.ID = @OtherID
          AND b.ID IS NOT NULL
          AND e.ID = 1;
END ELSE BEGIN
    SELECT b.Column1, 
           b.Column2, 
           b.Column3, 
           b.Column4, 
           b.Column5, 
           c.Column1, 
           b.Column1, 
           e.Column1
    FROM Table1 AS b
         INNER JOIN Table2 AS t ON b.ID = t.ID
         LEFT JOIN [LINKED SERVER].[DB].dbo.Table3 AS c ON b.ID = c.ID
         LEFT JOIN Table4 AS e ON b.ID = e.ID
    WHERE b.ID = @ID
          AND b.ID = @OtherID
          AND b.ID IS NOT NULL
          AND e.ID = 1;
END
_
1
Denis Rubashkin