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

パラメータスニッフィング、埋め込み、およびRECOMPILEオプション

    パラメータスニッフィング

    クエリのパラメータ化により、キャッシュされた実行プランの再利用が促進されるため、不要なコンパイルが回避され、プランキャッシュ内のアドホッククエリの数が削減されます。

    これらはすべて良いものであり、提供されます パラメータ化されるクエリは、実際には、異なるパラメータ値に対して同じキャッシュされた実行プランを使用する必要があります。 1つのパラメータ値に対して効率的な実行プランは、そうではない場合があります。 他の可能なパラメータ値の良い選択です。

    パラメータスニッフィングが有効になっている場合(デフォルト)、SQL Serverは、コンパイル時に存在する特定のパラメータ値に基づいて実行プランを選択します。暗黙の前提は、パラメーター化されたステートメントが最も一般的に最も一般的なパラメーター値で実行されるということです。これは十分に合理的に聞こえ(明白でさえあります)、実際、それはしばしばうまく機能します。

    キャッシュされたプランの自動再コンパイルが発生すると、問題が発生する可能性があります。再コンパイルは、キャッシュされたプランで使用されているインデックスが削除されたなど、さまざまな理由でトリガーされる可能性があります(正確性 再コンパイル)または統計情報が変更されたため(最適性 再コンパイル)。

    正確な原因が何であれ 計画の再コンパイルの場合、非定型 新しいプランが生成されるときに、値がパラメーターとして渡されます。これにより、(スニッフィングされた非定型パラメータ値に基づいて)新しいキャッシュプランが作成される可能性がありますが、これは再利用される実行の大部分には適していません。

    特定の実行プランがいつ再コンパイルされるかを予測するのは簡単ではありません(たとえば、統計が十分に変更されたため)。その結果、高品質の再利用可能なプランが、非定型のパラメーター値用に最適化されたまったく異なるプランに突然置き換えられる可能性があります。

    このようなシナリオの1つは、非定型値の選択性が高く、少数の行に対して最適化されたプランが得られる場合に発生します。このような計画では、多くの場合、シングルスレッド実行、ネストされたループ結合、およびルックアップが使用されます。このプランを、はるかに多くの行を生成するさまざまなパラメータ値に再利用すると、深刻なパフォーマンスの問題が発生する可能性があります。

    パラメータスニッフィングを無効にする

    文書化されたトレースフラグ4136を使用して、パラメータスニッフィングを無効にできます。トレースフラグは、クエリごとでもサポートされています。 QUERYTRACEONを介して使用する クエリヒント。どちらもSQLServer2005 Service Pack 4以降に適用されます(Service Pack 3に累積的な更新を適用する場合は少し前に適用されます)。

    SQL Server 2016以降、パラメータスニッフィングをデータベースレベルで無効にすることもできます。 、PARAMETER_SNIFFINGを使用 ALTER DATABASE SCOPED CONFIGURATIONへの引数 。

    パラメータスニッフィングが無効になっている場合、SQLServerは平均分布を使用します 実行計画を選択するための統計。

    これも合理的なアプローチのように聞こえますが(そして、計画が異常に選択的なパラメーター値に対して最適化される状況を回避するのに役立ちます)、それも完全な戦略ではありません。「平均」値に対して最適化された計画は、最終的には一般的に見られるパラメータ値に対しては、深刻に最適ではありません。

    並べ替えやハッシュなどのメモリを消費する演算子を含む実行プランについて考えてみます。クエリの実行が開始される前にメモリが予約されているため、平均分散値に基づくパラメータ化されたプランが tempdbに流出する可能性があります。 オプティマイザーが期待するよりも多くのデータを生成する一般的なパラメーター値の場合。

    サーバーに空きメモリの量に関係なく、通常、クエリの実行中にメモリ予約を増やすことはできません。特定のアプリケーションは、パラメータースニッフィングをオフにすることでメリットがあります(例については、Dynamics AXパフォーマンスチームによるこのアーカイブ投稿を参照してください)。

    ほとんどのワークロードでは、パラメータスニッフィングを完全に無効にすることが間違った解決策です。 、そして災害でさえあるかもしれません。パラメータスニッフィングはヒューリスティックな最適化です。ほとんどの場合、ほとんどのシステムで平均値を使用するよりもうまく機能します。

    クエリのヒント

    SQL Serverには、パラメータスニッフィングの動作を調整するためのさまざまなクエリヒントやその他のオプションが用意されています。

    • OPTIMIZE FOR (@parameter = value) クエリヒントは、特定の値に基づいて再利用可能な計画を作成します。
    • OPTIMIZE FOR (@parameter UNKNOWN) 特定のパラメータの平均分布統計を使用します。
    • OPTIMIZE FOR UNKNOWN すべてのパラメータに平均分布を使用します(トレースフラグ4136と同じ効果)。
    • WITH RECOMPILE ストアドプロシージャオプションは、実行ごとに新しいプロシージャプランをコンパイルします。
    • OPTION (RECOMPILE) クエリヒントは、個々のステートメントの新しい計画をまとめます。

    「パラメータの非表示」の古い手法 (プロシージャパラメータをローカル変数に割り当て、代わりに変数を参照する)は、OPTIMIZE FOR UNKNOWNを指定するのと同じ効果があります。 。 SQL Server 2008より前のインスタンスで役立つ場合があります(OPTIMIZE FOR ヒントは2008年の新機能です。

    すべて パラメータ化されたステートメントは、パラメータ値に対する感度をチェックし、そのままにしておくか(デフォルトの動作が適切に機能する場合)、上記のオプションのいずれかを使用して明示的にヒントを与える必要があります。

    考えられるすべてのパラメータ値に対して包括的な分析を実行するには時間がかかり、非常に高度なスキルが必要になるため、これが実際に行われることはめったにありません。
    ほとんどの場合、そのような分析は実行されず、パラメータ感度の問題は次のように対処されます。それらが本番環境で発生したとき。

    この事前分析の欠如は、おそらくパラメータスニッフィングの評判が悪い主な理由の1つです。問題が発生する可能性を認識し、非定型のパラメーター値を使用して再コンパイルしたときにパフォーマンスの問題を引き起こす可能性のあるステートメントに対して、少なくとも迅速な分析を実行することは有益です。

    パラメータとは何ですか?

    SELECTと言う人もいます ローカル変数を参照するステートメントは、「パラメーター化されたステートメント」です。 ある種の、しかしそれはSQLServerが使用する定義ではありません。

    ステートメントがパラメーターを使用していることの合理的な兆候は、プランのプロパティを調べることで見つけることができます(パラメーターを参照)。 Sentry OnePlanExplorerのタブ。または、SSMSのクエリプランルートノードをクリックして、プロパティを開きます。 ウィンドウをクリックし、パラメータリストを展開します ノード):

    「コンパイルされた値」は、キャッシュされたプランのコンパイルに使用されたパラメータのスニッフィングされた値を示します。 「ランタイム値」は、プランでキャプチャされた特定の実行のパラメータの値を示します。

    これらのプロパティのいずれかが空白であるか、さまざまな状況で欠落している可能性があります。クエリがパラメータ化されていない場合、プロパティはすべて失われます。

    SQL Serverには単純なものがないという理由だけで、パラメーターリストにデータを入力できる状況がありますが、ステートメントはパラメーター化されていません。これは、SQL Serverが単純なパラメーター化(後で説明)を試みたが、その試みが「安全ではない」と判断した場合に発生する可能性があります。その場合、パラメーターマーカーは存在しますが、実行プランは実際にはパラメーター化されていません。

    スニッフィングはストアドプロシージャだけのものではありません

    パラメータスニッフィングは、バッチがsp_executesqlを使用して再利用するために明示的にパラメータ化されている場合にも発生します。 。

    例:

    EXECUTE sys.sp_executesql
        N'
        SELECT
            P.ProductID,
            P.Name,
            TotalQty = SUM(TH.Quantity)
        FROM Production.Product AS P
        JOIN Production.TransactionHistory AS TH
            ON TH.ProductID = P.ProductID
        WHERE
            P.Name LIKE @NameLike
        GROUP BY
            P.ProductID,
            P.Name;
        ',
        N'@NameLike nvarchar(50)',
        @NameLike = N'K%';

    オプティマイザは、@NameLikeのスニッフィングされた値に基づいて実行プランを選択します パラメータ。パラメータ値「K%」は、Productのごく少数の行に一致すると推定されます テーブルなので、オプティマイザはネストされたループ結合とキールックアップ戦略を選択します:

    「[H-R]%」のパラメーター値(さらに多くの行に一致します)を使用してステートメントを再度実行すると、キャッシュされたパラメーター化されたプランが再利用されます。

    EXECUTE sys.sp_executesql
        N'
        SELECT
            P.ProductID,
            P.Name,
            TotalQty = SUM(TH.Quantity)
        FROM Production.Product AS P
        JOIN Production.TransactionHistory AS TH
            ON TH.ProductID = P.ProductID
        WHERE
            P.Name LIKE @NameLike
        GROUP BY
            P.ProductID,
            P.Name;
        ',
        N'@NameLike nvarchar(50)',
        @NameLike = N'[H-R]%';

    AdventureWorks サンプルデータベースは小さすぎてパフォーマンスを損なうことはありませんが、この計画は2番目のパラメータ値には確かに最適ではありません。

    プランキャッシュをクリアし、2番目のクエリを再度実行することで、オプティマイザーが選択したプランを確認できます。

    より多くの一致が予想されるため、オプティマイザーはハッシュ結合とハッシュ集約がより良い戦略であると判断します。

    T-SQL関数

    パラメータスニッフィングはT-SQL関数でも発生しますが、実行プランの生成方法によってこれがわかりにくくなる可能性があります。

    一般にT-SQLスカラー関数とマルチステートメント関数を避けるのには十分な理由があるため、教育目的でのみ、テストクエリのT-SQLマルチステートメントテーブル値関数バージョンを次に示します。

    CREATE FUNCTION dbo.F
        (@NameLike nvarchar(50))
    RETURNS @Result TABLE
    (
        ProductID   integer NOT NULL PRIMARY KEY,
        Name        nvarchar(50) NOT NULL,
        TotalQty    integer NOT NULL
    )
    WITH SCHEMABINDING
    AS
    BEGIN
        INSERT @Result
        SELECT
            P.ProductID,
            P.Name,
            TotalQty = SUM(TH.Quantity)
        FROM Production.Product AS P
        JOIN Production.TransactionHistory AS TH
            ON TH.ProductID = P.ProductID
        WHERE
            P.Name LIKE @NameLike
        GROUP BY
            P.ProductID,
            P.Name;
     
        RETURN;
    END;

    次のクエリは、関数を使用して、「K」で始まる製品名の情報を表示します。

    SELECT
        Result.ProductID,
        Result.Name,
        Result.TotalQty
    FROM dbo.F(N'K%') AS Result;

    SQL Serverは、関数の呼び出しごとに個別の実行後(実際の)クエリプランを返さないため、埋め込み関数を使用してパラメーターのスニッフィングを確認することはより困難です。関数は1つのステートメント内で何度も呼び出すことができ、SSMSが1つのクエリに対して100万の関数呼び出しプランを表示しようとしても、ユーザーは感心しません。

    この設計上の決定の結果として、テストクエリに対してSQLServerから返される実際の計画はあまり役に立ちません。

    それにもかかわらず、 組み込み関数を使用して動作中のパラメータースニッフィングを確認する方法。ここで使用する方法として選択したのは、プランキャッシュを検査することです。

    SELECT
        DEQS.plan_generation_num,
        DEQS.execution_count,
        DEQS.last_logical_reads,
        DEQS.last_elapsed_time,
        DEQS.last_rows,
        DEQP.query_plan
    FROM sys.dm_exec_query_stats AS DEQS
    CROSS APPLY sys.dm_exec_sql_text(DEQS.plan_handle) AS DEST
    CROSS APPLY sys.dm_exec_query_plan(DEQS.plan_handle) AS DEQP
    WHERE
        DEST.objectid = OBJECT_ID(N'dbo.F', N'TF');

    この結果は、関数プランが1回実行され、論理読み取りが201回、経過時間が2891マイクロ秒であり、最新の実行で1行が返されたことを示しています。返されたXMLプラン表現は、パラメータ値がだったことを示しています。 スニッフィング:

    次に、別のパラメーターを使用して、ステートメントを再度実行します。

    SELECT
        Result.ProductID,
        Result.Name,
        Result.TotalQty
    FROM dbo.F(N'[H-R]%') AS Result;

    実行後の計画では、関数によって306行が返されたことが示されています:

    プランキャッシュクエリは、関数のキャッシュされた実行プランが再利用されたことを示します(execution_count =2):

    また、前回の実行と比較して、論理読み取りの数がはるかに多く、経過時間が長くなっています。これは、ネストされたループとルックアッププランの再利用と一致しますが、完全に確実に、実行後の関数プランは拡張イベントを使用してキャプチャできます。 またはSQLServerプロファイラー ツール:

    パラメータスニッフィングは関数に適用されるため、これらのモジュールは、ストアドプロシージャに一般的に関連するパフォーマンスの同じ予期しない変更に悩まされる可能性があります。

    たとえば、関数が初めて参照されるときに、並列処理を使用しないプランがキャッシュされる場合があります。並列処理の恩恵を受ける(ただし、キャッシュされたシリアルプランを再利用する)パラメーター値を使用した後続の実行では、パフォーマンスが予期せず低下します。

    これまで見てきたように、SQL Serverは関数呼び出しに対して個別の実行後プランを返さないため、この問題を特定するのは難しい場合があります。 拡張イベントの使用 またはプロファイラー 実行後の計画を定期的にキャプチャすることは、非常に多くのリソースを消費する可能性があるため、その手法を非常に的を絞った方法で使用することはしばしば理にかなっています。関数のパラメーター感度の問題をデバッグすることの難しさは、関数が本番環境に到達する前に分析(および防御的なコーディング)を行う価値があることを意味します。

    パラメータスニッフィングは、T-SQLスカラーユーザー定義関数とまったく同じように機能します(インラインでない限り、SQL Server 2019以降)。インラインテーブル値関数は、(名前が示すように)コンパイル前に呼び出し元のクエリにインライン化されるため、呼び出しごとに個別の実行プランを生成しません。

    スニッフィングされたNULLに注意

    プランのキャッシュをクリアし、推定をリクエストします (実行前)テストクエリの計画:

    SELECT
        Result.ProductID,
        Result.Name,
        Result.TotalQty
    FROM dbo.F(N'K%') AS Result;

    2つの実行計画が表示されます。2つ目は関数呼び出し用です:

    推定計画に埋め込まれた関数によるパラメータースニッフィングの制限は、パラメーター値がNULLとしてスニッフィングされることを意味します (「K%」ではありません):

    2012より前のバージョンのSQLServerでは、この計画(NULL用に最適化) パラメータ)は再利用のためにキャッシュされますNULLであるため、これは残念なことです 代表的なパラメータ値である可能性は低く、クエリで指定された値ではありませんでした。

    SQL Server 2012(およびそれ以降)は、「推定プラン」要求の結果のプランをキャッシュしませんが、NULL用に最適化された機能プランは引き続き表示されます。 コンパイル時のパラメータ値。

    シンプルで強制的なパラメーター化

    クエリが単純なパラメーター化の対象となるか、強制パラメーター化のデータベースオプションが有効になっている(またはプランガイドを使用して同じ効果を得る)ため、定数リテラル値を含むアドホックT-SQLステートメントをSQLServerでパラメーター化できます。

    この方法でパラメーター化されたステートメントも、パラメータースニッフィングの対象になります。次のクエリは、単純なパラメータ化の対象となります。

    SELECT 
        A.AddressLine1, 
        A.City, 
        A.PostalCode 
    FROM Person.Address AS A 
    WHERE 
        A.AddressLine1 = N'Heidestieg Straße 8664';

    推定実行プランは、スニッフィングされたパラメータ値に基づいて2.5行の推定を示しています。

    実際、クエリは7行を返します(値がスニッフィングされている場合でも、カーディナリティの推定は完全ではありません):

    この時点で、このクエリがパラメータ化され、結果のパラメータ値がスニッフィングされたという証拠がどこにあるのか疑問に思われるかもしれません。別の値を使用してクエリをもう一度実行します:

    SELECT 
        A.AddressLine1, 
        A.City, 
        A.PostalCode 
    FROM Person.Address AS A 
    WHERE 
        A.AddressLine1 = N'Winter der Böck 8550';

    クエリは1行を返します:

    実行プランは、スニッフィングされた値を使用してコンパイルされたパラメーター化されたプランを再利用した2番目の実行を示しています。

    パラメータ化とスニッフィングは別々のアクティビティです

    アドホックステートメントは、SQLServerによってなしでパラメータ化できます。 スニッフィングされるパラメータ値。

    実例を示すために、トレースフラグ4136を使用して、サーバーによってパラメーター化されるバッチのパラメータースニッフィングを無効にすることができます。

    DBCC FREEPROCCACHE;
    DBCC TRACEON (4136);
    GO
    SELECT
        A.AddressLine1, 
        A.City, 
        A.PostalCode 
    FROM Person.Address AS A 
    WHERE
        A.AddressLine1 = N'Heidestieg Straße 8664';
    GO
    SELECT 
        A.AddressLine1, 
        A.City, 
        A.PostalCode 
    FROM Person.Address AS A 
    WHERE 
        A.AddressLine1 = N'Winter der Böck 8550';
    GO
    DBCC TRACEOFF (4136);

    スクリプトはパラメーター化されたステートメントになりますが、パラメーター値はカーディナリティ推定の目的でスニッフィングされません。これを確認するために、プランキャッシュを調べることができます:

    WITH XMLNAMESPACES
        (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
    SELECT
        DECP.cacheobjtype,
        DECP.objtype,
        DECP.usecounts,
        DECP.plan_handle,
        parameterized_plan_handle =
            DEQP.query_plan.value
            (
                '(//StmtSimple)[1]/@ParameterizedPlanHandle',
                'NVARCHAR(100)'
            )
    FROM sys.dm_exec_cached_plans AS DECP
    CROSS APPLY sys.dm_exec_sql_text(DECP.plan_handle) AS DEST
    CROSS APPLY sys.dm_exec_query_plan(DECP.plan_handle) AS DEQP
    WHERE 
        DEST.[text] LIKE N'%AddressLine1%'
        AND DEST.[text] NOT LIKE N'%XMLNAMESPACES%';

    結果には、アドホッククエリの2つのキャッシュエントリが表示され、パラメータ化されたプランハンドルによってパラメータ化された(準備された)クエリプランにリンクされています。

    パラメータ化された計画は2回使用されます:

    パラメータスニッフィングが無効になっているため、実行プランには異なるカーディナリティの見積もりが表示されます。

    1.44571行の推定値を、パラメータースニッフィングが有効になっているときに使用された2.5行の推定値と比較します。

    スニッフィングを無効にすると、推定値はAddressLine1に関する平均頻度情報から取得されます。 桁。 DBCC SHOW_STATISTICSの抜粋 問題のインデックスの出力は、この数がどのように計算されたかを示しています。テーブルの行数(19,614)に密度(7.370826e-5)を掛けると、1.44571行の推定値が得られます。

    補足: 単純なパラメーター化の対象となるのは、一意のインデックスを使用した整数比較のみであると一般に考えられています。私はそれを論駁するために意図的にこの例(一意でないインデックスを使用した文字列比較)を選択しました。

    RECOMPILEとOPTION(RECOMPILE)を使用

    パラメータ感度の問題が発生した場合、フォーラムやQ&Aサイトでよくあるアドバイスは、「再コンパイルを使用する」ことです(前述の他のチューニングオプションが不適切であると想定しています)。残念ながら、そのアドバイスは、WITH RECOMPILEを追加することを意味すると誤解されることがよくあります。 ストアドプロシージャのオプション。

    WITH RECOMPILEを使用する 事実上、SQL Server 2000の動作に戻ります。ここで、ストアドプロシージャ全体 実行するたびに再コンパイルされます。

    より良い代替案 、SQL Server 2005以降では、OPTION (RECOMPILE)を使用します。 ステートメントだけのクエリヒント これは、パラメータスニッフィングの問題に悩まされています。このクエリヒントにより、問題のあるステートメントが再コンパイルされます。 それだけ。ストアドプロシージャ内の他のステートメントの実行計画はキャッシュされ、通常どおり再利用されます。

    WITH RECOMPILEを使用する また、ストアドプロシージャのコンパイル済みプランがキャッシュされないことも意味します。その結果、sys.dm_exec_query_statsなどのDMVでパフォーマンス情報が維持されることはありません。 。

    代わりにクエリヒントを使用すると、コンパイルされたプランをキャッシュでき、パフォーマンス情報をDMVで利用できます(ただし、影響を受けるステートメントについてのみ、最新の実行に限定されます)。

    OPTION (RECOMPILE)を使用して、少なくともSQL Server 2008ビルド2746(累積的な更新プログラムを含むService Pack 1)を実行しているインスタンスの場合 もう1つの重要な利点があります WITH RECOMPILEOPTION (RECOMPILE)のみ パラメータ埋め込みの最適化を有効にします 。

    パラメータ埋め込みの最適化

    パラメーター値をスニッフィングすると、オプティマイザーはパラメーター値を使用してカーディナリティ推定値を導出できます。両方のWITH RECOMPILE およびOPTION (RECOMPILE) 結果として、各実行の実際のパラメータ値から計算された見積もりを含むクエリプランが作成されます。

    パラメータ埋め込みの最適化 このプロセスをさらに一歩進めます。クエリパラメータは置き換えられます クエリ解析中にリテラル定数値を使用します。

    パーサーは驚くほど複雑な単純化が可能であり、その後のクエリ最適化によってさらに洗練される可能性があります。 WITH RECOMPILEを特徴とする次のストアドプロシージャについて考えてみます。 オプション:

    CREATE PROCEDURE dbo.P
        @NameLike nvarchar(50),
        @Sort tinyint
    WITH RECOMPILE
    AS
    BEGIN
        SELECT TOP (5)
            ProductID,
            Name
        FROM Production.Product
        WHERE
            @NameLike IS NULL
            OR Name LIKE @NameLike
        ORDER BY
            CASE WHEN @Sort = 1 THEN ProductID ELSE NULL END ASC,
            CASE WHEN @Sort = 2 THEN ProductID ELSE NULL END DESC,
            CASE WHEN @Sort = 3 THEN Name ELSE NULL END ASC,
            CASE WHEN @Sort = 4 THEN Name ELSE NULL END DESC;
    END;
    >

    プロシージャは、次のパラメータ値を使用して2回実行されます。

    EXECUTE dbo.P
    	@NameLike = N'K%',
    	@Sort = 1;
    GO
    EXECUTE dbo.P
    	@NameLike = N'[H-R]%',
    	@Sort = 4;

    WITH RECOMPILE を使用すると、実行のたびにプロシージャが完全に再コンパイルされます。パラメータ値はスニッフィングされます 毎回、カーディナリティの推定値を計算するためにオプティマイザによって使用されます。

    最初のプロシージャ実行の計画は正確であり、1行を見積もります:

    2回目の実行では、実行時に見られる366行に非常に近い360行が推定されます。

    どちらのプランも同じ一般的な実行戦略を使用しています。WHEREを適用して、インデックス内のすべての行をスキャンします 残差としての節述語。 CASEを計算します ORDER BYで使用される式 句; トップNソートを実行します CASEの結果について 表現。

    オプション(再コンパイル)

    次に、OPTION (RECOMPILE)を使用してストアドプロシージャを再作成します。 WITH RECOMPILEの代わりにヒントをクエリ :

    CREATE PROCEDURE dbo.P
        @NameLike nvarchar(50),
        @Sort tinyint
    AS
    BEGIN
        SELECT TOP (5)
            ProductID,
            Name
        FROM Production.Product
        WHERE
            @NameLike IS NULL
            OR Name LIKE @NameLike
        ORDER BY
            CASE WHEN @Sort = 1 THEN ProductID ELSE NULL END ASC,
            CASE WHEN @Sort = 2 THEN ProductID ELSE NULL END DESC,
            CASE WHEN @Sort = 3 THEN Name ELSE NULL END ASC,
            CASE WHEN @Sort = 4 THEN Name ELSE NULL END DESC
        OPTION (RECOMPILE);
    END;

    以前と同じパラメータ値を使用してストアドプロシージャを2回実行すると、劇的に異なるが生成されます。 実行計画。

    これは最初の実行プランです(「K」で始まる名前を要求するパラメーターを使用し、ProductIDで並べ替えます 昇順):

    パーサーは埋め込み クエリテキストのパラメータ値。次の中間形式になります。

    SELECT TOP (5)
        ProductID,
        Name
    FROM Production.Product
    WHERE
        'K%' IS NULL
        OR Name LIKE 'K%'
    ORDER BY
        CASE WHEN 1 = 1 THEN ProductID ELSE NULL END ASC,
        CASE WHEN 1 = 2 THEN ProductID ELSE NULL END DESC,
        CASE WHEN 1 = 3 THEN Name ELSE NULL END ASC,
        CASE WHEN 1 = 4 THEN Name ELSE NULL END DESC;

    次に、パーサーはさらに進んで、矛盾を取り除き、CASEを完全に評価します。 式。その結果、次のようになります。

    SELECT TOP (5)
        ProductID,
        Name
    FROM Production.Product
    WHERE
        Name LIKE 'K%'
    ORDER BY
        ProductID ASC,
        NULL DESC,
        NULL ASC,
        NULL DESC;

    定数値による順序付けは許可されていないため、そのクエリをSQL Serverに直接送信しようとすると、エラーメッセージが表示されます。それにもかかわらず、これはパーサーによって生成されたフォームです。 パラメータ埋め込み最適化を適用した結果として発生したため、内部的に許可されています。 。簡略化されたクエリにより、クエリオプティマイザの作業が大幅に楽になります。

    クラスター化インデックススキャン LIKEを適用します 残差としての述語。 計算スカラー 定数NULLを提供します 値。 トップ Clustered Indexで指定された順序で最初の5行を返します (並べ替えを回避する)。完璧な世界では、クエリオプティマイザは Compute Scalarも削除します NULLsを定義します 、クエリの実行中には使用されないため。

    2回目の実行はまったく同じプロセスに従い、クエリプランが作成されます(「H」から「R」の文字で始まる名前の場合、Nameの順に並べられます。 降順)このように:

    このプランは、非クラスター化インデックスシークを特徴としています。 LIKEをカバーしています 範囲、残りのLIKE 述語、定数NULLs 前と同じように、そしてトップ(5)。クエリオプティマイザは、BACKWARDの実行を選択します インデックスシークでの範囲スキャン もう一度並べ替えを避けるため。

    上記のプランをWITH RECOMPILEを使用して作成されたプランと比較してください 、パラメータ埋め込み最適化を使用できません :

    このデモの例は、一連のIFとしてより適切に実装されている可能性があります。 プロシージャー内のステートメント(パラメーター値の組み合わせごとに1つ)。これにより、毎回ステートメントのコンパイルを行うことなく、同様のクエリプランの利点を提供できます。より複雑なシナリオでは、OPTION (RECOMPILE)によって提供されるパラメーターの埋め込みを使用してステートメントレベルで再コンパイルします。 非常に便利な最適化手法になる可能性があります。

    埋め込みの制限

    OPTION (RECOMPILE)を使用するシナリオが1つあります。 パラメータ埋め込みの最適化が適用されることはありません。ステートメントが変数に割り当てられている場合、パラメーター値は埋め込まれていません:

    CREATE PROCEDURE dbo.P
        @NameLike nvarchar(50),
        @Sort tinyint
    AS
    BEGIN
        DECLARE
            @ProductID integer,
            @Name nvarchar(50);
     
        SELECT TOP (1)
            @ProductID = ProductID,
            @Name = Name
        FROM Production.Product
        WHERE
            @NameLike IS NULL
            OR Name LIKE @NameLike
        ORDER BY
            CASE WHEN @Sort = 1 THEN ProductID ELSE NULL END ASC,
            CASE WHEN @Sort = 2 THEN ProductID ELSE NULL END DESC,
            CASE WHEN @Sort = 3 THEN Name ELSE NULL END ASC,
            CASE WHEN @Sort = 4 THEN Name ELSE NULL END DESC
        OPTION (RECOMPILE);
    END;

    SELECT ステートメントが変数に割り当てられるようになりました。生成されるクエリプランは、WITH RECOMPILEの場合と同じです。 使われた。パラメータ値は引き続きスニッフィングされ、カーディナリティ推定のためにクエリオプティマイザによって使用されます。OPTION (RECOMPILE) それでも、単一のステートメントのみをコンパイルし、パラメータの埋め込みの利点のみをコンパイルします。 失われます。


    1. PostgreSQL、ドラッグアンドスワップ

    2. SQLServerで同じクエリを複数回実行する最も簡単な方法

    3. 連続範囲でグループ化するにはどうすればよいですか

    4. 列から一意の値を選択する