2

5 つの入力パラメーターを受け取るストアド プロシージャがあります。手順は少し複雑で、実行には約 2 分かかります。クエリを最適化しています。

ですから、私の質問は、入力パラメーターをローカル変数に割り当ててから、プロシージャーでローカル変数を使用することは常に役立つのでしょうか?

もしそうなら、それはどのように役立ちますか?

4

3 に答える 3

11

パラメータ スニッフィングの詳細については説明しませんが、要するに、常に役立つとは限りません (また、妨げになる可能性もあります)。

主キーとインデックス付きの日付列 (A) を持つテーブル (T) を想像してください。テーブルには 1,000 行があり、400 行が同じ A の値を持ち (今日 20130122 としましょう)、残りの 600 行は次の 600 日です、したがって、日付ごとに 1 つのレコードのみです。

このクエリ:

SELECT *
FROM T
WHERE A = '20130122';

次の目的で異なる実行計画が生成されます。

SELECT *
FROM T
WHERE A = '20130123';

統計は、1,000 行のうち最初の 400 行が返されることを示しているため、オプティマイザはテーブル スキャンがブックマーク ルックアップよりも効率的であると認識する必要があります。もっと効率的。

さて、質問に戻りますが、これを手順にした場合:

CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param

次に実行します

EXECUTE dbo.GetFromT '20130122'; --400 rows

テーブル スキャンを含むクエリ プランが使用されます。最初に実行するときに「20130123」をパラメータとして使用すると、ブックマーク ルックアップ プランが保存されます。手順が再コンパイルされるまで、計画は同じままです。このようなことをする:

CREATE PROCEDURE dbo.GetFromT @Param VARCHAR(5)
AS
    DECLARE @Param2 VARCHAR(5) = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2

次に、これが実行されます:

EXECUTE dbo.GetFromT '20130122';

プロシージャは一度にコンパイルされますが、適切に流れないため、最初のコンパイルで作成されたクエリ プランは @Param2 が @param と同じになることを認識していません。 expect) は、300 が返される (30%) と想定します。これは、テーブル スキャンがブックマーク ルックアップよりも効率的であると見なすためです。パラメータとして '20130123' を使用して同じ手順を実行すると、(最初に呼び出されたパラメータに関係なく) 同じ計画が生成されます。これは、統計が未知の値に使用できないためです。したがって、'20130122' に対してこのプロシージャを実行するとより効率的になりますが、他のすべての値に対しては、ローカル パラメータを使用しない場合よりも効率が低下します (ローカル パラメータを使用しないプロシージャが最初に '20130122' 以外で呼び出されたと仮定します)。


自分で実行計画を表示できるように、いくつかのクエリを示します。

スキーマとサンプル データを作成する

CREATE TABLE T (ID INT IDENTITY(1, 1) PRIMARY KEY, A DATE NOT NULL, B INT,C INT, D INT, E INT);

CREATE NONCLUSTERED INDEX IX_T ON T (A);

INSERT T (A, B, C, D, E)
SELECT  TOP 400 CAST('20130122' AS DATE), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   type = 'P'
UNION ALL
SELECT TOP 600 DATEADD(DAY, number, CAST('20130122' AS DATE)), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   Type = 'P';
GO
CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param
GO
CREATE PROCEDURE dbo.GetFromT2 @Param DATE
AS
    DECLARE @Param2 DATE = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2
GO

実行手順 (実際の実行計画を表示):

EXECUTE GetFromT '20130122';
EXECUTE GetFromT '20130123';
EXECUTE GetFromT2 '20130122';
EXECUTE GetFromT2 '20130123';
GO
EXECUTE SP_RECOMPILE GetFromT;
EXECUTE SP_RECOMPILE GetFromT2;
GO
EXECUTE GetFromT '20130123';
EXECUTE GetFromT '20130122';
EXECUTE GetFromT2 '20130123';
EXECUTE GetFromT2 '20130122';

初めてGetFromTコンパイルされたときにテーブル スキャンが使用され、パラメーター '20130122' で実行されたときにこれが保持され、テーブル スキャンが使用され、GetFromT2'20130122' の計画が保持されることがわかります。

プロシージャが再コンパイル用に設定され、再度実行された後 (別の順序で注意してください)、GetFromTブックマーク ループアップを使用し、「20130122」の計画を保持しますが、以前はテーブル スキャンがより適切な計画であると見なされていました。GetFromT2は順序の影響を受けず、再コンパイル前と同じ計画を持っています。

つまり、要約すると、データの分布とインデックス、再コンパイルの頻度、およびプロシージャがローカル変数を使用することでメリットが得られるかどうかの運次第です。それは確かに常に役立つとは限りません。


うまくいけば、ローカル パラメーター、実行プラン、およびストアド プロシージャのコンパイルを使用することの効果を明らかにすることができました。完全に失敗した場合、または重要なポイントを見逃した場合は、ここでさらに詳細な説明を見つけることができます。

http://www.sommarskog.se/query-plan-mysteries.html

于 2013-01-22T22:56:38.470 に答える
2

私はそうは思いません。最新のコンピューター アーキテクチャには、ストアド プロシージャ値を格納するためのプロセッサの近くに十分なキャッシュがあります。基本的に、これらはローカル キャッシュ メモリにロードされる「スタック」上にあると考えることができます。

出力パラメータがある場合、入力値をローカル変数にコピーすると、間接化のステップが 1 つなくなる可能性があります。ただし、インダイレクションが初めて実行されると、宛先メモリはローカル キャッシュに置かれ、おそらくそこに残ります。

いいえ、これは重要な最適化ではないと思います。

ただし、これが役立つかどうかを確認するために、ストアド プロシージャのさまざまなバリアントの時間をいつでも確認できます。

于 2013-01-22T22:28:35.270 に答える