「コスト」が時間の面であると仮定すると(;-の面で他に何ができるかはわかりませんが)、少なくとも次のようなことを行うことでそれを理解できるはずです。
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
SET STATISTICS TIME ON;
EXEC sp_help 'sys.databases'; -- replace with your proc
SET STATISTICS TIME OFF;
[メッセージ]タブで報告される最初の項目は次のとおりです。
SQL Serverの解析およびコンパイル時間:
これを少なくとも10回実行し、「CPU」ミリ秒と「経過」ミリ秒の両方を平均します。
理想的には、これを実稼働環境で実行して、正確な時間の見積もりを取得できるようにしますが、実稼働環境でプランキャッシュをクリアできることはほとんどありません。幸いなことに、SQL Server 2008以降では、キャッシュから特定の計画をクリアできるようになりました。その場合、次のことができます。
DECLARE @SQL NVARCHAR(MAX) = '';
;WITH cte AS
(
SELECT DISTINCT stat.plan_handle
FROM sys.dm_exec_query_stats stat
CROSS APPLY sys.dm_exec_text_query_plan(stat.plan_handle, 0, -1) qplan
WHERE qplan.query_plan LIKE N'%sp[_]help%' -- replace "sp[_]help" with proc name
)
SELECT @SQL += N'DBCC FREEPROCCACHE ('
+ CONVERT(NVARCHAR(130), cte.plan_handle, 1)
+ N');'
+ NCHAR(13) + NCHAR(10)
FROM cte;
PRINT @SQL;
EXEC (@SQL);
SET STATISTICS TIME ON;
EXEC sp_help 'sys.databases' -- replace with your proc
SET STATISTICS TIME OFF;
ただし、「悪い」キャッシュプランの原因となるパラメーターに渡される値の変動性に応じて、OPTION(RECOMPILE)
との間の中間点であると考える別の方法がありますOPTION(OPTIMIZE FOR UNKNOWN)
ます:動的SQL。はい、私はそれを言った。また、パラメータ化されていない動的SQLを意味します。その理由は次のとおりです。
少なくとも1つ以上の入力パラメーター値に関して、明らかに不均一な分布のデータがあります。上記のオプションの欠点は次のとおりです。
OPTION(RECOMPILE)
すべての実行計画を生成し、あなたが恩恵を受けることができることはありませんいかなる再び渡されたパラメータ値は、前の実行(S)と同じであっても、計画の再利用。頻繁に(数秒に1回またはそれ以上の頻度で)呼び出されるprocの場合、これは時折の恐ろしい状況からあなたを救いますが、それでも常にそれほど素晴らしい状況ではありません。
OPTION(OPTIMIZE FOR (@Param = value))
その特定の値に基づいて計画を生成します。これにより、いくつかのケースに役立つ可能性がありますが、それでも現在の問題を受け入れることができます。
OPTION(OPTIMIZE FOR UNKNOWN)
平均分布の量に基づいて計画を生成します。これは、一部のクエリには役立ちますが、他のクエリには損害を与えます。これは、ローカル変数を使用するオプションと同じである必要があります。
ただし、動的SQLを正しく実行すると、渡されるさまざまな値に、理想的な(まあ、できる限り)独自のクエリプランを持たせることができます。ここでの主なコストは、渡される値の種類が増えると、キャッシュ内の実行計画の数が増え、メモリを占有することです。軽微な費用は次のとおりです。
したがって、ここでは、1秒間に複数回呼び出され、それぞれが数百万行の複数のテーブルにヒットするprocがあったときに、この状況をどのように管理しているかを示します。私が試しましたOPTION(RECOMPILE)
が、これはパラメータスニッフィング/不良キャッシュプランの問題を持たないケースの99%でプロセスに非常に有害であることが判明しました。また、これらのprocの1つには約15個のクエリがあり、そのうち3〜5個だけがここで説明したように動的SQLに変換されたことに留意してください。特定のクエリに必要でない限り、動的SQLは使用されませんでした。
ストアドプロシージャへの入力パラメーターが複数ある場合、どのパラメーターが非常に異なるデータ分布を持つ列で使用され(したがって、この問題が発生する)、どのパラメーターがより均等な分布を持つ列で使用される(そうすべきではない)かを把握するこの問題の原因)。
均等に分散された列に関連付けられたproc入力パラメーターのパラメーターを使用して、動的SQL文字列を作成します。このパラメーター化により、このクエリに関連するキャッシュ内の実行計画の増加を削減できます。
非常に多様な分布に関連付けられている残りのパラメーターについては、それらをリテラル値として動的SQLに連結する必要があります。一意のクエリはクエリテキストへの変更によって決定されるため、持つことWHERE StatusID = 1
は、持つこととは異なるクエリ、したがって異なるクエリプランWHERE StatusID = 2
です。
クエリのテキストに連結されるproc入力パラメーターのいずれかが文字列の場合、SQLインジェクションから保護するために検証する必要があります(ただし、渡される文字列がユーザーではなくアプリですが、まだ)。少なくともREPLACE(@Param, '''', '''''')
、単一引用符がエスケープされた単一引用符になることを確認するために行います。
必要に応じて、ユーザーの作成に使用される証明書を作成し、ストアドプロシージャに署名して、直接テーブルアクセス許可が新しい証明書ベースのユーザーにのみ付与され、[public]
そうでない場合はそのようなアクセス許可を持たないユーザーに付与されるようにします。
procの例:
CREATE PROCEDURE MySchema.MyProc
(
@Param1 INT,
@Param2 DATETIME,
@Param3 NVARCHAR(50)
)
AS
SET NOCOUNT ON;
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = N'
SELECT tab.Field1, tab.Field2, ...
FROM MySchema.SomeTable tab
WHERE tab.Field3 = @P1
AND tab.Field8 >= CONVERT(DATETIME, ''' +
CONVERT(NVARCHAR(50), @Param2, 121) +
N''')
AND tab.Field2 LIKE N''' +
REPLACE(@Param3, N'''', N'''''') +
N'%'';';
EXEC sp_executesql
@SQL,
N'@P1 INT',
@P1 = @Param1;