web-dev-qa-db-ja.com

パラメータがローカル変数に格納されている場合の実行計画の改善

2つのストアドプロシージャがあります。これは信じられないほど速い(〜2秒)

CREATE PROCEDURE [schema].[Test_fast]
   @week date
AS
BEGIN

    declare @myweek date = @week

    select distinct serial 
    from [schema].[tEventlog]         as e
    join [schema].tEventlogSourceName as s on s.ID = e.FKSourceName
    where s.SourceName = 'source_name'
        and (e.EventCode = 1 or e.EventCode = 9)
        and cast(@myweek as datetime2(3)) <= [Date] 
        and [Date] < dateadd(day, 7, cast(@myweek as datetime2(3)))    
END

そして、これは遅く実行されます(〜2時間):

create PROCEDURE [schema].[Test_slow]
   @week date
AS
BEGIN

    select distinct serial 
    from [schema].[tEventlog]         as e
    join [schema].tEventlogSourceName as s on s.ID = e.FKSourceName
    where s.SourceName = 'source_name'
        and (e.EventCode = 1 or e.EventCode = 9)
        and cast(@week as datetime2(3)) <= [Date] 
        and [Date] < dateadd(day, 7, cast(@week as datetime2(3)))
END

唯一の実際の違いは、次の行です(ローカル変数@myweekを使用)。

declare @myweek date = @week

実行計画は次のとおりです。最初の計画は[schema]。[Test_fast]からのもので、2番目の計画は[schema]。[Test_slow]からのものです。

enter image description here

私の質問は次のとおりです。パラメーターを取得してローカル変数に格納し、このローカル変数を使用すると、SQL Server 2012がより優れた実行プランを(より速く)取得するのはなぜですか。統計またはインデックスに問題があるか? (また、なぜ2番目の実行プランがどのような種類の並列実行も使用していないのかと思います)。

[〜#〜]更新[〜#〜]

2つのSPに同じパラメーターを与え、それらを同じ時間(ほぼ2秒の時間差)で開始しました。これは、このDBの統計の自動更新ではありません。

例:

EXEC    [schema].[Test_fast]
        @week = '2016-02-08'

EXEC    [schema].[Test_slow]
        @week = '2016-02-08'

実行計画は次のとおりです。

https://Gist.github.com/anonymous/6e404f896d9613c2061a#file-sp_execution_plan-sqlplan

インデックスをさらに更新しても効果はありません。

8
Leon

ローカル変数を使用すると、パラメーター値の盗聴が防止されるため、クエリはaverage分布統計に基づいてコンパイルされます。これは、OPTION (OPTIMIZE FOR UNKNOWN)および トレースフラグ4136 が使用可能になる前の、一部のタイプのパラメータ感度問題の回避策でした。

提供された実行プランから、これはまさにあなたのケースで起こったことです。

ローカル変数が使用されている場合、変数の値は盗聴できません。

No sniffing

空白の「コンパイルされた値」に注意してください。クエリオプティマイザーは、Date列の値の平均分布(またはおそらく完全な推測)に基づいて、より多くの行を並列に導きます予定。

ストアドプロシージャパラメータを直接使用する場合、@ weekの値が傍受されます。

Sniffed

オプティマイザーは、以下にプラグインされた値「2016-02-08」を使用して、クエリ述語に一致する行数を推定します。

and cast(@week as datetime2(3)) <= [Date] 
and [Date] < dateadd(day, 7, cast(@week as datetime2(3)))

それは1行の見積もりで出て来、キールックアップを含むシリアルプランの選択につながります。上記の述部は、カーディナリティーの見積もりにはあまり適していません。そのため、1行の見積もりはあまり正確ではない可能性があります。トレースフラグ4199を有効にしてみることもできますが、推定値が改善される保証はありません。

詳細については、以下をご覧ください。

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

一般に、ストアード・プロシージャーの最初の実行は、@ weekの非常に選択的な値で行われ、期待される行数が少ない場合もあります。問題の別の考えられる原因は、統計がこの範囲の値をカバーするように更新される前に、@ callの非常に最近の値が最初の呼び出しで使用された場合に発生します(これは 昇順キー問題 です)。

@weekの非常に選択的なスニッフィング値により、クエリオプティマイザーは、インデックスシークとキールックアップを含む非並列プランを選択する場合があります。このプランは、異なるパラメーター値でプロシージャを将来実行するために再利用するためにキャッシュされます。 (@weekの値が異なる)後の実行で元の行よりもはるかに多くの行が選択される場合、シーク+キー検索はもはや適切な戦略ではないため、プランのパフォーマンスが低下する可能性があります。

16
Paul White 9