SQL Server 2012で古典的な Parameter Sniffing の問題に遭遇しました。いくつかの調査に基づいて、この問題の周りに複数のオプションが見つかりました。違いを理解するために必要な2つのオプションは、OPTION(OPTIMIZE FOR UNKNOWN)
とOPTION(RECOMPILE)
です。
この問題が発生しているクエリの最後でOPTION(RECOMPILE)
を使用することをためらっています。これは、サーバーに毎回新しい実行プランを生成させるためです。このクエリを頻繁に呼び出すと、そのマシンのCPUが急上昇します。
彼が利用できる最良のソリューションを使用するために、2つのオプションの実際の違いは何ですか?
OPTION(OPTIMIZE FOR UNKNOWN)
は毎回再コンパイルする代わりにキャッシュを再利用しますか?
OPTION(OPTIMIZE FOR UNKNOWN)
は毎回再コンパイルする代わりにキャッシュを再利用しますか?
はい、そうです。
[〜#〜] msdn [〜#〜] からのこの引用からわかるように、OPTION(OPTIMIZE FOR UNKNOWN)
とOPTION(RECOMPILE)
の間には2つの主な違いがあります。
_
OPTIMIZE FOR UNKNOWN
_クエリをコンパイルして最適化するときに、強制パラメーター化で作成されたパラメーターを含め、すべてのローカル変数の初期値ではなく統計データを使用するようにクエリオプティマイザーに指示します。
RECOMPILE
SQL Serverデータベースエンジンに対して、クエリの実行後に生成されたプランを破棄するように指示します。クエリオプティマイザーは、同じクエリが次に実行されるときにクエリプランを強制的に再コンパイルします。
RECOMPILE
を指定しないと、データベースエンジンはクエリプランをキャッシュして再利用します。クエリプランをコンパイルするとき、RECOMPILE
クエリヒントはクエリ内のローカル変数の現在の値を使用し、クエリがストアドプロシージャ内にある場合は、現在の値が任意のパラメーターに渡されます。
したがって、2つの主な違いは次のとおりです。
通常、生成されたクエリプランはキャッシュされ、再利用されます。 _OPTIMIZE FOR UNKNOWN
_は、エンジンのこの機能には影響しません。 RECOMPILE
はこの機能を抑制し、エンジンにプランを破棄してキャッシュに入れないように指示します。
通常、オプティマイザはパラメータ値を「嗅ぎ」、計画を生成するときにこれらの値を使用します。 _OPTIMIZE FOR UNKNOWN
_はこの機能を抑制し、すべてのパラメーターを値が不明であるかのように処理するようにエンジンに指示します。オプティマイザーには、さまざまなフィルタリング基準に利用可能な統計を使用する方法とヒューリスティックが組み込まれています。詳細は 最適化…平凡? を参照してください。通常、パラメータースニッフィングは、クエリ/ストアドプロシージャの最初の実行時に使用され、最初の実行中にパラメーターの値を使用します。生成されたプランはキャッシュされ、後で再利用できます。
ここで覚えておくべき非自明なことの1つは、両方の場合(通常、クエリヒントなし、および_OPTIMIZE FOR UNKNOWN
_ヒントあり)で、生成されたプランが有効であり、anyの正しい結果を生成する必要があることです可能なパラメーター値。通常/ヒントなしのケースで、最初の実行時に使用されたスニッフィングされた値に合わせて調整されます。 _OPTIMIZE FOR UNKNOWN
_の場合は特定の値に合わせて調整されていませんが、後でパラメーターが変更された場合は引き続き有効です。
これは重要であり、オプティマイザが計画の特定の変換および簡略化を実行するのを防ぎます。
OPTION(RECOMPILE)
を使用すると、オプティマイザは各実行中にパラメータの実際の値をインライン化でき、オプティマイザはパラメータの実際の値を使用してより適切な計画を生成します。プランがキャッシュされて再利用されないため、生成されたプランが他のパラメーター値で機能しない可能性があることを心配する必要はありません。
この効果は、主に 動的検索条件 クエリで表示されます。例えば:
_SELECT ...
FROM T
WHERE
(@ParamSomeID = 0)
OR
(
@ParamSomeID = -1
AND
T.SomeID NOT IN
(
SELECT OtherTable.SomeID
FROM OtherTable
)
)
OR
(
T.SomeID IN
(
SELECT OtherTable.SomeID
FROM OtherTable
WHERE OtherTable.SomeID = @ParamSomeID
)
)
OPTION(RECOMPILE)
_
_@ParamSomeID
_が_0
_の場合、オプティマイザはクエリにWHERE
句がまったくないかのように処理します。この計画では、OtherTable
についてはまったく触れられていません。
_@ParamSomeID
_が_-1
_の場合、プランは、左反セミ結合を使用してT
をOtherTable
に結合し、OtherTable
全体をスキャンします。
_@ParamSomeID
_がたとえば5の場合、プランはOtherTable
の一意のインデックスでインデックスシークを実行し、OtherTable
から1行のみを読み取ります。
OPTION(RECOMPILE)
がないと、この種の単純化と変換は起こりません。
OPTION(RECOMPILE)
を使用するもう1つの理由は、データ分布が非常に歪んでいる場合です。たとえば、100万行のテーブルがあるとします。 1つの列の値は、990K行で0、1K行で1〜10です。この列でフィルターをかけるクエリには、フィルターの実際の値に応じて異なるプランが必要です。
上記の両方の例で、_OPTIMIZE FOR UNKNOWN
_は平凡な計画を生成します。
OPTION(OPTIMIZE FOR UNKNOWN)は毎回再コンパイルする代わりにキャッシュを再利用しますか?
はい。不明の最適化は、プランの生成方法に影響します(つまり、明示的にパラメーターをスニッフィングして列データのヒストグラムと比較します)が、生成されると、プランはキャッシュに残り、再利用されます。
OPTION(RECOMPILE)
は、実行のたびに強制的に再コンパイルします。これは、かなり強引なアプローチです。これは、各クエリが異なり、複雑で、実行時間がかなり長い分析DW/BI環境でのみ意味があります。
あなたはあなたの処分で他のオプションも持っています:
これらの両方で、投稿と同じ効果を得ることができますが、非侵襲的な方法で行われます(アプリのコードやクエリの変更はありません)。
私は両方を使用しました。 OPTION(OPTIMIZE FOR UNKNOWN)
は、さまざまなパラメーターを取り込む重い検索のストアドプロシージャに使用されました。私にはわからない特定の条件(統計とそうでないもの)があり、最適化が無効になり、クエリは平凡でしたが、深刻な遅延(およびタイムアウト)が発生しました。 OPTION(OPTIMIZE FOR UNKNOWN)
はこの問題を解決しましたが、理想的ではありませんでした。
同じ重い検索手順では断続的な問題が発生します。つまり、数か月後に検索がタイムアウトします。当面の解決策は、_sp_recompile
_を呼び出すことです。これは、OPTION(RECOMPILE)
句をストアドプロシージャに追加することと同義です。
ストアドプロシージャの根性が"入力した結果"ソリューションを推進しました。このソリューションでは、3回のキーストロークごとにDB検索がトリガーされ、結果がドロップダウンに表示されます。
最後に、OPTION(OPTIMIZE FOR UNKNOWN)
を削除し、_EXEC sp_recompile<sp>
_を毎晩のメンテナンスジョブに追加するだけで、すべての問題が解決しました。