そうですね - 私は解決策について詳しく説明するように求められました (そして、「優れた回答を書くためのヒント」も参照しています) - それを説明しようとしましょう - 私は効果しか説明できませんが - 背景を理解するには、自分自身を突っ込む必要があります言及された記事に。
特定のデータのスナップショットを 3 分ごとに作成するという問題に直面しました。(非常に単純化された)クエリは
SELECT *
FROM table
WHERE TimeStamp > DateAdd(ss,-180,GetDate())
完璧に機能しました-関数に入れます:
CREATE FUNCTION GetSnapshot (@ss int) RETURNS TABLE
AS
RETURN
SELECT *
FROM Table
WHERE TimeStamp > DateAdd(ss,-@ss,GetDate())
これは、定数などで呼び出す限り、完璧に機能しました
SELECT *
FROM GetSnapshot(180)
180 秒ではすべての目的に適合しないため、さらにパラメータ化したいと考えました。ここで問題が始まりました:
DECLARE @v int
SET @v = 180
SELECT *
FROM GetSnapshot(@v)
180での直接呼び出しには数ミリ秒かかるのに対し、ほぼ10秒で実行されます
同じ効果が単純なテーブルにも当てはまることにも言及する必要があります-関数を呼び出したという事実は結果に影響しませんでした。何を試しても-10秒。
今、完全に絶望する前に、タイトルにある質問でスタックオーバーフローの専門家に相談しました。プログラミング言語でのパラメーターの受け渡しについてはよく知っていますが、SQL については何も知りません。PL では、値渡しの場合、コンパイラは実行時に実際の値のローカル コピーを作成するコードを生成し、それを定数のように呼び出された関数に渡します。一方、参照渡しは言語構造を「そのまま」渡します。呼び出されたプロシージャは、変数や関数呼び出しなど、このパラメーターを何度も「呼び出す」ことができます。したがって、SQL コンパイラーは値による定数の呼び出しと参照による変数の呼び出しを行うという印象を受けました。これは、呼び出されたプロシージャーによる複数回の評価を必要とし、数万レコードの結果セットで実行時間が長いことを説明します。
前述の記事で、Erland は多かれ少なかれ私が行った方法で説明しています。
(見積もり開始)
- 定数は定数であり、クエリに定数が含まれている場合、SQL Server は定数の値を完全に信頼して使用できます。また、制約から行がアクセスできないと推測できる場合は、そのようなショートカットを使用してテーブルにまったくアクセスしないことさえできます。返されます。
- パラメータの場合、SQL Server は実行時の値を認識しませんが、クエリをコンパイルするときに入力値を「スニッフィング」します。
- ローカル変数の場合、SQL Server は実行時の値をまったく認識せず、標準的な仮定を適用します。(どの仮定が行われるかは、演算子と、一意のインデックスの存在から推測できるものによって異なります。)
(引用終了)
彼はさらに、パラメーターのスニッフィングと実行計画について詳しく説明していますが、私は何も理解していなかったことを認めて恥ずかしくありません (-:) - しかし、全体として、プログラミング言語の値による/参照の概念に似ています。
SQL Server に「値による呼び出し」を強制するにはどうすればよいですか?
幸いなことに、Demystifying SQL Server への David のヒントがありました: SQL Server Parameter Sniffingの記事は、私が賞賛した解決策を提供します: 完全な「参照による」呼び出しを sp_executesql ラッパーでパックします。参照」ですが、パラメーターの解決はラッパーレベルで行われるため、内部呼び出しは「値によって」行うことができ、ミリ秒の応答時間に戻ります.
このように使用してトリックを作成します。
exec sp_executesql
N'SELECT * FROM GetSnapshot(@v)',
N'@v int',
@v=180
それだけです - 応答が遅くなって申し訳ありませんが、私は最後の日はとても忙しかったです... Meiki