web-dev-qa-db-ja.com

フィールド=パラメータORパラメータIS NULLパターン

次のような述語で記述されたストアドプロシージャに関連するパラメータスニッフィングの問題を認識しています。

CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    SELECT [Field] FROM [dbo].[Table]
    WHERE [Field] = @Parameter 
    OR @Parameter IS NULL;
END;

最初の実行時のパラメーターの値(スカラーまたはNULL)に応じて、反対の値には最適とは言えないプランがキャッシュされます。

[フィールド]がスカラーであり、テーブルのクラスタリングインデックスであると仮定します。クエリをサポートするストアドプロシージャを作成する次のアプローチの長所と短所は何ですか。

同じストアドプロシージャでの条件付き選択

CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    IF(@Parameter IS NOT NULL) BEGIN;
        SELECT [Field]
        FROM [dbo].[Table]
        WHERE [Field] = @Parameter;
    END;
    ELSE BEGIN;
        SELECT [Field]
        FROM [dbo].[Table];
    END;
END;

ストアドプロシージャ内の動的SQL

CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    DECLARE @sql NVARCHAR(MAX) = N'';
    SET @sql += N'SELECT [Field]'
    SET @sql += N'FROM [dbo].[Table]';

    IF(@Parameter IS NOT NULL) BEGIN;
        @sql += N'WHERE [Field] = @Parameter';
    END;

    SET @sql += N';';

    EXEC sp_executesql @sql N'@Parameter INT', @Parameter;
END;

個別のストアドプロシージャ

CREATE PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    SELECT [Field]
    FROM [dbo].[Table]
    WHERE [Field] = @Parameter;
END;

CREATE PROCEDURE [dbo].[GetAll] AS BEGIN;
    SELECT [Field]
    FROM [dbo].[Table];
END;
7
M. Jacobson

アーロンバートランド、マーティンスミス、およびロブファーリーからの以前の回答とコメントに基づいています。追加のアプローチOPTION(RECOMPILE)を含む、各アプローチの賛成/反対のリストをまとめたかったのです。


同じストアドプロシージャでの条件付き選択

マーティン・スミスの返答から:

「条件付き選択」オプションは、依然としてパラメータスニッフィングに対して脆弱です。 @Parameterがnullのときにプロシージャが最初に実行される場合、[Field] = @Parameter述語を使用した分岐は、1行を推定します(= NULL述語に期待される0から切り上げられます)。

  • 再コンパイルの費用はかかりません。
  • すべてのステートメントとストアドプロシージャのキャッシュの再利用を計画します。
  • キャッシュされたプランは、@ ParameterがNULLでない場合に結果セットに大きな差異がない場合でも、パラメーターを傍受する脆弱性があります。
  • パラメータの数が増えると、管理上適切にスケーリングされません。
  • すべてのT-SQLのIntellisense。

ストアドプロシージャ内の動的SQL

ロブファーリーから:

ますます多くのパラメーターを取得すると、初心者には恐ろしく見えますが、動的SQLオプションが最も明確になります。

  • 再コンパイルの費用はかかりません。
  • すべてのステートメントとストアドプロシージャのキャッシュの再利用を計画します。
  • キャッシュされたプランは、@ ParameterがNOT NULLであるときに結果セットに大きな差異がある場合にのみ、パラメータースニッフィングに対して脆弱です。
  • パラメータの数が増えるにつれて、管理上適切にスケーリングします。
  • すべてのT-SQLでIntellisenseを提供するわけではありません。

個別のストアドプロシージャ

  • 再コンパイルの費用はかかりません。
  • すべてのステートメントとストアドプロシージャのキャッシュの再利用を計画します。
  • キャッシュされたプランは、@ ParameterがNOT NULLであるときに結果セットに大きな差異がある場合にのみ、パラメータースニッフィングに対して脆弱です。
  • パラメータの数が増えると、管理上適切にスケーリングされません。
  • すべてのT-SQLのIntellisense。

OPTION(RECOMPILE)

マーティン・スミスから:

毎回コンパイルを犠牲にして毎回ランタイム値の最適な計画を取得するため。

  • 再コンパイルのCPUコスト。
  • ステートメントの後にOPTION(RECOMPILE)が続くプランキャッシュの再利用はなく、ストアドプロシージャとOPTION(RECOMPILE)のないステートメントのみが再利用されます。
  • パラメータの数が増えるにつれて、管理上適切にスケーリングします。
  • パラメータスニッフィングに対して脆弱ではありません。
  • すべてのT-SQLのIntellisense。

私の個人的な持ち帰り

@Parameterの異なるスカラー値を使用した結果セットに大きな差異がない場合、動的SQLは最高のパフォーマンスを発揮し、システムオーバーヘッドが最も少なく、OPTION(RECOMPILE)と比較して管理オーバーヘッドに関してわずかに悪いだけです。より複雑なシナリオでは、パラメーター値の変動が結果セットに大きな変化を引き起こす可能性があるため、OPTION(RECOMPILE)の条件付きの包含または除外で動的SQLを使用することが、全体的に最高のパフォーマンスを発揮します。アプローチの詳細については、アーロンバートランドの記事へのリンクを次に示します。 https://blogs.sentryone.com/aaronbertrand/backtobasics-updated-kitchen-sink-example/

2
M. Jacobson

SQL Serverのかなり最近のビルドを使用している場合、考えられるもう1つのオプションは

SELECT [Field]
FROM   [dbo].[Table]
WHERE  [Field] = @Parameter
        OR @Parameter IS NULL
OPTION (RECOMPILE); 

毎回コンパイルを犠牲にして毎回ランタイム値の最適な計画を取得するため。

「条件付き選択」オプションは、依然としてパラメータスニッフィングに対して脆弱です。 @Parameterがnullのときにプロシージャが最初に実行される場合、[Field] = @Parameter述語のあるブランチは1行を推定します(=NULL述語に期待される0から切り上げ)。

質問の特定の例では、単一の列を選択していて、これによってフィルタリングしているのと同じ列である場合、問題が発生することはほとんどありませんが、他の場合には発生する可能性があります。

例えば次の例では、[dbo].[Get] 1への最初の呼び出しで、333,731の論理読み取りが行われます。これは、キールックアップを含む不適切なプランを選択するためです。プランがキャッシュから削除され、最初に渡された1を使用して再コンパイルされると、論理読み取りは4,330に減少します

DROP TABLE IF EXISTS [Table]

GO

CREATE TABLE [Table]
(
[Field1]  INT INDEX IX,
[Field2]  INT,
[Field3]  INT,
);

INSERT INTO [Table]
SELECT TOP 1000000 CRYPT_GEN_RANDOM(1)%3, CRYPT_GEN_RANDOM(4), CRYPT_GEN_RANDOM(4)
FROM sys.all_objects o1, sys.all_objects o2

GO

CREATE OR ALTER PROCEDURE [dbo].[Get] @Parameter INT = NULL AS BEGIN;
    IF(@Parameter IS NOT NULL) BEGIN;
        SELECT *
        FROM [dbo].[Table]
        WHERE [Field1] = @Parameter;
    END;
    ELSE BEGIN;
        SELECT *
        FROM [dbo].[Table];
    END;
END;

GO

SET STATISTICS TIME ON
SET STATISTICS IO ON


EXEC [dbo].[Get] 

EXEC [dbo].[Get] 1;

declare @plan_handle varbinary(64) = (select plan_handle from sys.dm_exec_procedure_stats where object_id = object_id('[dbo].[Get]'));

--Remove the plan from the cache 
DBCC FREEPROCCACHE (@plan_handle);  

--Re-execute it with NOT NULL passed first
EXEC [dbo].[Get] 1;
5
Martin Smith

それらはすべて優れています。本当に。これらはすべて、キャッシュに2つのプランが存在するという同じ影響を及ぼします。

ますます多くのパラメーターを取得すると、初心者には恐ろしく見えますが、動的SQLオプションが最も明確になります。

これが関数である場合、QOがより適切に機能できるように、複数ステートメントのオプションを回避することをお勧めします。

5
Rob Farley