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


15

SQL Server 2012および2016 Standard:

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

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

回答:


27

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

いいえ、すべてのバージョンをキャッシュます。むしろ、渡された変数でコンパイルされたすべてのパスが探索された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'は存在しません。

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

ナッツ

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

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

少し簡単なテストは、ストアドプロシージャの推定プランを取得することです。オプティマイザーが両方のパスを探索していることがわかります。

ナッツ

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

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

別のもので再実行する場合@Reputation

EXEC dbo.YourMom @Reputation = 2;

実際の計画から:

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

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

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

ナッツ

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

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

あなたがいる場合したい分岐したロジックのこの種を、あなたはサブストアドプロシージャを呼び出す必要があるだろう。

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;

お役に立てれば!

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