sql >> データベース >  >> RDS >> Sqlserver

ストアドプロシージャの入力パラメータをローカル変数に割り当てると、クエリを最適化できますか?

    パラメータスニッフィングの詳細については説明しませんが、要するに、常にではありません。 助けてください(そしてそれは妨げる可能性があります)。

    主キーとインデックス付きの日付列(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行が返されることが示されているため、オプティマイザーは、テーブルスキャンがブックマークルックアップよりも効率的であるのに対し、2番目の行では1行しか生成されないため、ブックマークルックアップが多くなることを認識します。より効率的です。

    さて、これを手順にした場合、あなたの質問に戻りましょう:

    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と同じになることを認識していないため、オプティマイザ(期待)は、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



    1. ORA-01422を取得する理由:正確なフェッチで、要求された数を超える行が返される

    2. Oracleでユーザーとテーブル名の組み合わせに引用符で囲まれた識別子を使用するにはどうすればよいですか?

    3. PostgreSQLエラー'サーバーに接続できませんでした:そのようなファイルまたはディレクトリはありません'

    4. MySQLとSQLAlchemyORMを使用した高数値精度フロート