web-dev-qa-db-ja.com

SQL Server-ストアドプロシージャとプランキャッシュのロジック

SQL Server 2012および2016 Standard:

if-elseパラメータの値に応じて、コードの2つのブランチの1つを実行するストアドプロシージャのロジックエンジンは最新バージョンをキャッシュしますか?

そして、次の実行時にパラメータの値が変更された場合、コードの別のブランチを実行する必要があるため、ストアドプロシージャを再コンパイルして再キャッシュしますか? (このクエリはコンパイルに非常にコストがかかります。)

15
Nicole G.

SQL Server 2012および2016 Standard:パラメーターの値に応じて、コードの2つのブランチの1つを実行するためにストアドプロシージャにif-elseロジックを配置すると、エンジンは最新バージョンをキャッシュしますか?

いいえ、すべてのバージョンをキャッシュします。むしろ、変数を渡してコンパイルされたすべてのパスが探索された1つのバージョンをキャッシュします。

Stack Overflowデータベースを使用した簡単なデモを次に示します。

インデックスを作成します。

CREATE INDEX ix_yourmom ON dbo.Users (Reputation) INCLUDE (Id, DisplayName);
GO 

分岐コードで、存在しないインデックスを指すインデックスヒントを含むストアドプロシージャを作成します。

CREATE OR ALTER PROCEDURE dbo.YourMom (@Reputation INT)
AS 
BEGIN

    IF @Reputation = 1
    BEGIN
        SELECT u.Id, u.DisplayName, u.Reputation
        FROM dbo.Users AS u WITH (INDEX = PK_Users_Id)
        WHERE u.Reputation = @Reputation;
    END;

    IF @Reputation > 1
    BEGIN
        SELECT u.Id, u.DisplayName, u.Reputation
        FROM dbo.Users AS u WITH (INDEX = ix_yourdad)
        WHERE u.Reputation = @Reputation;

    END;

END;

そのストアドプロシージャを実行してReputation = 1を探すと、エラーが発生します。

EXEC dbo.YourMom @Reputation = 1;

メッセージ308、レベル16、状態1、プロシージャYourMom、行14 [バッチ開始行32]テーブル 'dbo.Users'(FROM句で指定)のインデックス 'ix_yourdad'は存在しません。

インデックス名を修正してクエリを再実行すると、 キャッシュされたプラン は次のようになります。

Nuts

内部では、XMLには@Reputation変数への2つの参照があります。

<ColumnReference Column="@Reputation" ParameterDataType="int" ParameterCompiledValue="(1)" />

少し簡単なテストは、ストアドプロシージャの推定プランを取得することです。両方のパスを調査しているオプティマイザを確認できます。

Nuts

そして、次の実行でパラメータの値が変化した場合、コードの別のブランチを実行する必要があるため、ストアドプロシージャを再コンパイルして再キャッシュしますか? (このクエリのコンパイルにはかなりの費用がかかります。)ありがとうございます。

いいえ、最初のコンパイルのランタイム値を保持します。

別の@Reputationで再実行すると:

EXEC dbo.YourMom @Reputation = 2;

実際の計画 から:

<ColumnReference Column="@Reputation" ParameterDataType="int" ParameterCompiledValue="(1)" ParameterRuntimeValue="(2)" />

コンパイルされた値は1のままですが、実行時の値は2になっています。

私の会社が開発しているような無料のツール sp_BlitzCache でチェックアウトできるプランキャッシュで:

Nuts

ストアドプロシージャが2回呼び出され、その中の各ステートメントが1回呼び出されました。

それで私たちは何を持っていますか?ストアドプロシージャの両方のクエリに対して1つのキャッシュされたプラン。

このような分岐ロジックをwantする場合は、サブストアドプロシージャを呼び出す必要があります。

CREATE OR ALTER PROCEDURE dbo.YourMom (@Reputation INT)
AS 
BEGIN

    IF @Reputation = 1
    BEGIN

        EXEC dbo.Reputation1Query;

    END;

    IF @Reputation > 1
    BEGIN

        EXEC dbo.ReputationGreaterThan1Query;

    END;

END;

または動的SQL:

DECLARE @sql NVARCHAR(MAX) = N''

SET @sql +=
N'
SELECT u.Id, u.DisplayName, u.Reputation
        FROM dbo.Users AS u '
IF @Reputation = 1
BEGIN
    SET @sql += N' (INDEX = PK_Users_Id)
        WHERE u.Reputation = @Reputation;'
END;


IF @Reputation > 1 
BEGIN

SET @sql += ' WITH (INDEX = ix_yourmom)
        WHERE u.Reputation = @Reputation;'

END;


EXEC sys.sp_executesql @sql;

お役に立てれば!

27
Erik Darling