OPTION(RECOMPILE)は常に高速です。どうして?


169

追加するという奇妙な状況に遭遇しました OPTION (RECOMPILE)クエリすると0.5秒で実行さしましたが、省略するとクエリに5分以上かかります。

これは、クエリがQuery AnalyzerまたはC#プログラムからを介して実行される場合SqlCommand.ExecuteReader()です。呼び出す(または呼び出さない)DBCC FREEPROCCACHEまたはDBCC dropcleanbuffers違いはありません。クエリ結果は常に瞬時に返されOPTION (RECOMPILE)、それがない場合は5分を超えます。クエリは常に[このテストのために]同じパラメータで呼び出されます。

SQL Server 2008を使用しています。

私はSQLの記述にはかなり慣れていますがOPTION、以前にクエリでコマンドを使用したことがなく、このフォーラムの投稿をスキャンするまではプランキャッシュの概念全体に精通していませんでした。投稿から私の理解は、それOPTION (RECOMPILE)は高価な操作です。それは明らかにクエリの新しいルックアップ戦略を作成します。それでは、なぜそれを省略した後続のクエリOPTION (RECOMPILE)が非常に遅いのですか?後続のクエリは、再コンパイルのヒントを含む以前の呼び出しで計算されたルックアップ戦略を利用するべきではありませんか?

呼び出しごとに再コンパイルのヒントを必要とするクエリがあるのは非常に珍しいことですか?

エントリーレベルの質問で申し訳ありませんが、私は本当にこれの表裏を作ることができません。

更新:クエリの投稿を求められました...

select acctNo,min(date) earliestDate 
from( 
    select acctNo,tradeDate as date 
    from datafeed_trans 
    where feedid=@feedID and feedDate=@feedDate 

    union 

    select acctNo,feedDate as date 
    from datafeed_money 
    where feedid=@feedID and feedDate=@feedDate 

    union 

    select acctNo,feedDate as date 
    from datafeed_jnl 
    where feedid=@feedID and feedDate=@feedDate 
)t1 
group by t1.acctNo
OPTION(RECOMPILE)

クエリアナライザーからテストを実行するとき、次の行を付加します。

declare @feedID int
select @feedID=20

declare @feedDate datetime
select @feedDate='1/2/2009'

C#プログラムから呼び出す場合、パラメーターはSqlCommand.Parametersプロパティを介して渡されます。

この議論の目的のために、パラメーターが決して変わらないと仮定することができるので、次善のパラメーターの臭いを原因として除外することができます。


3
クエリのパラメーターは何ですか?この記事をチェックしてください。 blogs.msdn.com/b/turgays/archive/2013/09/10/… 基本的に、SQLは、プロシージャが最初にコンパイルされたときに、パラメータに基づいてクエリプランを生成しようとします。異なる、おそらくより現実的なパラメーターを渡し始めると、最適ではない計画が生成される可能性があります
Sparky

3
クエリはここにリストするのに十分簡潔ですか?Sparkyは正しいと思いますし、おそらくパラメーターのスニッフィングに関連していると思います。同様の問題があり、この優れた記事を読むまで私を混乱させていました:sommarskog.se/query-plan-mysteries.html
Chris

1
ただし、この場合(このテストのため)は常に同じパラメーターを渡します。他のアプリがこっそり侵入して、他のパラメーターを使用してクエリを呼び出すことができませんでした。記事をありがとう。レビューします。
Chad Decker

2
これは、パラメーターと変数の値をスニッフィングするか、より単純化するために発生します。大きいの単純化の例としては崩壊するだろうX = @X OR @X IS NULLX=@Xと求めて行ってここを参照してくださいまたはウィンドウ関数を持つビューに対してさらに下述部のプッシュ
マーティン・スミス

3
編集後、クエリアナライザーの例では、パラメーターではなく変数を使用します。これらの値は、を除いて盗聴されることはありませんRECOMPILE。いずれにしても、実行計画をキャプチャして、違いを確認してください。
マーティン・スミス

回答:


157

使用OPTION(RECOMPILE)が意味をなす場合があります。私の経験では、これが実行可能なオプションであるのは、動的SQLを使用している場合のみです。これがあなたの状況で理にかなっているかどうかを調べる前に、統計を再構築することをお勧めします。これは、以下を実行することで実行できます。

EXEC sp_updatestats

そして、実行計画を再作成します。これにより、実行計画が作成されたときに最新の情報が使用されます。

追加OPTION(RECOMPILE)すると、クエリが実行されるたびに実行プランが再構築されます。このように説明されcreates a new lookup strategyたことは聞いたことがありませんが、同じものに対して異なる用語を使用しているだけかもしれません。

ストアドプロシージャが作成されたとき(.NETからアドホックSQLを呼び出していると思われますが、パラメーター化されたクエリを使用している場合、これは最終的にストアドプロシージャコールになります)SQL Serverはこのクエリの最も効果的な実行プランを決定しようとしますデータベース内のデータと渡されたパラメーターに基づいて(パラメータースニッフィング)、このプランをキャッシュします。つまり、データベースに10個のレコードがあるクエリを作成し、100,000,000個のレコードがあるときにそれを実行すると、キャッシュされた実行プランが最も効果的でなくなる可能性があります。

まとめ- OPTION(RECOMPILE)ここでメリットになる理由は何もありません。統計と実行計画を更新する必要があるだけだと思います。統計の再構築は、状況によってはDBAの作業の重要な部分になる場合があります。統計を更新しても問題が解決しない場合は、両方の実行計画を投稿することをお勧めします。

そして、あなたの質問に答えるために-はい、クエリを実行するたびに実行プランを再コンパイルすることが最善のオプションであることは非常に珍しいと思います。


22
はい、sp_updatestatsがうまくいきました。クエリが最初に10レコードのテーブルで実行され、現在テーブルには数百万のレコードが含まれていると説明したときに、頭に釘を打ちました。それはまさに私の場合でした。重要だとは思わなかったので、投稿では触れませんでした。魅力的なもの。再度、感謝します。
Chad Decker

3
SQLは常に1つの行が含まれていると考えるので、テーブル変数を操作するために私が見つけた唯一の方法です。数千の行が含まれていると問題になります。
Alex Zhukovskiy 2016

4
興味深い1つの詳細:統計の更新は、これらの統計を使用するすべてのキャッシュされたプランを暗黙的に無効にしますが、更新アクション後に統計が実際に変更された場合に限らます。したがって、非常にゆがんだ読み取り専用テーブルの場合、明示的なOPTION (RECOMPILE)解決策が唯一の解決策であると思われます。
Groo、

141

多くの場合、クエリの実行ごとに大幅な違いがある場合、それは多くの場合5つの問題の1つであることがわかります。

  1. 統計学-統計が古くなっています。データベースは、テーブルとインデックスのさまざまな列の値のタイプの範囲と分布に関する統計を格納します。これは、クエリエンジンがクエリを実行するための攻撃の "計画"を作成するのに役立ちます。たとえば、ハッシュを使用したり、セット全体を調べたりして、テーブル間のキーを照合するために使用するメソッドのタイプです。データベース全体、または特定のテーブルやインデックスのみに対してUpdate Statisticsを呼び出すことができます。統計が古くなっていると、クエリプランが同じクエリに対して新しく挿入または変更されたデータに最適ではない可能性があるため、クエリの実行速度が遅くなります(以下でさらに詳しく説明します)。サンプリングするデータの量によっては、オーバーヘッドが発生し、速度が低下し、遅延するため、本番データベースで統計をすぐに更新することは適切でない場合があります。フルスキャンまたはサンプリングを使用して統計を更新することもできます。クエリプランを確認すると、次のコマンドを使用して、使用中のインデックスの統計を表示することもできます。DBCC SHOW_STATISTICS(tablename、indexname)。これにより、クエリプランがそのアプローチの基礎として使用しているキーの分布と範囲が表示されます。

  2. パラメータのスニッフィング -キャッシュされたクエリプランは、クエリ自体が変更されていなくても、渡した特定のパラメータに対して最適ではありません。たとえば、1,000,000行のうち10行しか取得しないパラメーターを渡す場合、作成されるクエリプランはハッシュ結合を使用できますが、渡すパラメーターが1,000,000行の750,000を使用する場合、作成されるプランはインデックススキャンまたはテーブルスキャン。このような状況では、SQLステートメントにオプションOPTION(RECOMPILE)を使用するか、SPでWITH RECOMPILEを使用するように指示できます。エンジンにこれを「シングルユースプラン」と伝えるため、適用されない可能性が高いキャッシュプランを使用しないでください。この決定を行う方法についての規則はありません。それは、ユーザーによるクエリの使用方法を知っているかどうかに依存します。

  3. INDEXES-クエリは変更されていない可能性がありますが、非常に有用なインデックスの削除など、他の場所での変更により、クエリの速度が低下しました。

  4. ROWS CHANGED-照会する行は呼び出しごとに大幅に変化します。通常、これらの場合、統計は自動的に更新されます。ただし、動的SQLを構築している場合や、タイトループ内でSQLを呼び出している場合は、誤った大幅な行数または統計に基づいて古いクエリプランを使用している可能性があります。この場合も、OPTION(RECOMPILE)が役立ちます。

  5. ロジックそのロジック、クエリはもはや効率的ではありません、それは少数の行には問題ありませんでしたが、もはやスケールしません。これには通常、クエリプランのより詳細な分析が含まれます。たとえば、物事をまとめて行うことはできませんが、チャンクする必要があり、小さなコミットを行う必要があります。または、クロス積は、小さなセットでは問題ありませんでしたが、スケールが大きくなるにつれてCPUとメモリを占有します。これは、 DISTINCTを使用すると、すべての行に対して関数を呼び出します。CASTING型変換またはNULLまたは関数のため、キーの一致ではインデックスを使用しません...可能性が多すぎます。

一般に、クエリを作成するときは、特定のデータがテーブル内で大まかにどのように分布しているかについて、いくらか心構えが必要です。たとえば、列は、均等に分散された数の異なる値を持つことができます。または、分布が時間の経過とともに頻繁に変化するか、かなり静的であるかに関係なく、80%の時間に特定の値のセットがある場合があります。これにより、効率的なクエリを作成する方法がわかりやすくなります。しかし、クエリのパフォーマンスをデバッグするときは、なぜそれが遅いか非効率的であるかに関する仮説を立てる根拠があります。


2
ありがとう、友達。これはすばらしい情報です。私が最初に質問を投稿したとき、私はあなたの答えを理解することができなかったでしょうが、今ではそれは完全に理にかなっています。
Chad Decker 2014年

3
PARAMETER SNIFFINGは、私の存在にとって断然最大の悩みの種です。面接の質問が失敗するまで、私はこのコマンドについてさえ知りませんでした。パラメータスニッフィングに対する私の解決策は、常にパラメータ値をハッシュし、「AND {hash} = {hash}」を追加することでした。これにより、SQLは常に値が異なるようになりました。ハックですが、うまくいきました。
ジェレミーボイド

27

OPTION(RECOMPILE)が非常に役立つ状況の優れたリスト(@CodeCowboyOrgから提供)に追加するには、

  1. テーブル変数。テーブル変数を使用している場合、テーブル変数の事前に作成された統計は存在しないため、クエリプランの推定行と実際の行の間に大きな差が生じることがよくあります。テーブル変数を使用したクエリでOPTION(RECOMPILE)を使用すると、関連する行番号の見積もりがはるかに優れたクエリプランを生成できます。OPTION(RECOMPILE)を追加するまで、使用できずに放棄しようとしていたテーブル変数を特にクリティカルに使用しました。実行時間は数時間から数分になりました。これはおそらく珍しいことですが、いずれにしても、テーブル変数を使用して最適化に取り組んでいる場合は、OPTION(RECOMPILE)が違いをもたらすかどうかを確認する価値があります。

1
5つのテーブル変数を持つクエリがあります。私のマシンでは、30分以上実行されます。私の同僚のマシンでは、1秒未満で実行されます。マシンのハードウェアは類似しており、SQL Serverのバージョンは同じです。両方にOPTION(RECOMPILE)を追加すると、両方のマシンで2秒で実行されます。すべての場合において、実行テストはSSMSで実行されます。この違いの原因は何ですか?
アダム

1
Option(再コンパイル)を使用せずに、自分のマシンと同僚のマシンで実行プランを比較できますか?それは違いの原因を示しているかもしれません。
DWright 2016年

1
一時テーブルの場合、それは同じ状況ですか?
Muflix 2016

1
@muflix:いい質問ですね。一時テーブルにも統計があり、エンジンは他のテーブルと同じように自動再コンパイルを選択するはずなので、効果は同じだとは思いません(確信はありません)。多分誰かがより確実に知っています。
DWright

2
一時テーブルの統計は自動的に更新または再コンパイルされないため、プログラマーはそれを行う必要があります。
J.マイケルヴュルト2017年

1

クエリを調整する前の最初のアクションは、インデックスと統計をデフラグ/再構築することです。それ以外の場合は、時間を浪費しています。

実行プランをチェックして、安定しているかどうかを確認する必要があります(パラメーターを変更しても同じです)。安定していない場合は、カバーインデックス(この場合は各テーブルに対して)を作成する必要がある場合があります(作成できるシステムを知っている他のクエリにも役立ちます)。

例:create index idx01_datafeed_trans On datafeed_trans(feedid、feedDate) INCLUDE(acctNo、tradeDate)

プランが安定しているか安定している場合は、sp_executesql( 'sql statement')を使用して文を実行し、固定実行プランを保存して使用できます。

プランが不安定な場合は、アドホックステートメントまたはEXEC( 'sqlセンテンス')を使用して、毎回実行プランを評価および作成する必要があります。(または「再コンパイル付き」のストアドプロシージャ)。

それが役に立てば幸い。


1

この質問を壊滅させますが、誰も考慮していないように思われる説明があります。

統計-統計が利用できない、または誤解を招く

次のすべてが当てはまる場合:

  1. feedid列とfeedDate列は高い相関性がある可能性があります(たとえば、フィードIDはフィード日付よりも具体的であり、日付パラメーターは冗長な情報です)。
  2. 両方の列が順次列である索引はありません。
  3. これらの両方の列をカバーする手動で作成された統計はありません。

次に、SQLサーバーは、列が無相関であると誤って想定している可能性があり、制限と選択された実行プランの両方を適用するためのカーディナリティの推定が予想より低くなります。この場合の修正は、2つの列をリンクする統計オブジェクトを作成することですが、コストのかかる操作ではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.