次のような述語で記述されたストアドプロシージャに関連するパラメータスニッフィングの問題を認識しています。
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;
アーロンバートランド、マーティンスミス、およびロブファーリーからの以前の回答とコメントに基づいています。追加のアプローチOPTION(RECOMPILE)を含む、各アプローチの賛成/反対のリストをまとめたかったのです。
同じストアドプロシージャでの条件付き選択
マーティン・スミスの返答から:
「条件付き選択」オプションは、依然としてパラメータスニッフィングに対して脆弱です。 @Parameterがnullのときにプロシージャが最初に実行される場合、[Field] = @Parameter述語を使用した分岐は、1行を推定します(= NULL述語に期待される0から切り上げられます)。
ストアドプロシージャ内の動的SQL
ロブファーリーから:
ますます多くのパラメーターを取得すると、初心者には恐ろしく見えますが、動的SQLオプションが最も明確になります。
個別のストアドプロシージャ
OPTION(RECOMPILE)
マーティン・スミスから:
毎回コンパイルを犠牲にして毎回ランタイム値の最適な計画を取得するため。
私の個人的な持ち帰り
@Parameterの異なるスカラー値を使用した結果セットに大きな差異がない場合、動的SQLは最高のパフォーマンスを発揮し、システムオーバーヘッドが最も少なく、OPTION(RECOMPILE)と比較して管理オーバーヘッドに関してわずかに悪いだけです。より複雑なシナリオでは、パラメーター値の変動が結果セットに大きな変化を引き起こす可能性があるため、OPTION(RECOMPILE)の条件付きの包含または除外で動的SQLを使用することが、全体的に最高のパフォーマンスを発揮します。アプローチの詳細については、アーロンバートランドの記事へのリンクを次に示します。 https://blogs.sentryone.com/aaronbertrand/backtobasics-updated-kitchen-sink-example/
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;
それらはすべて優れています。本当に。これらはすべて、キャッシュに2つのプランが存在するという同じ影響を及ぼします。
ますます多くのパラメーターを取得すると、初心者には恐ろしく見えますが、動的SQLオプションが最も明確になります。
これが関数である場合、QOがより適切に機能できるように、複数ステートメントのオプションを回避することをお勧めします。