web-dev-qa-db-ja.com

パラメータスニッフィングvs変数vs再コンパイルvs最適化(不明)

そのため、今朝問題が発生する長時間実行プロシージャがありました(30秒+実行時間)。パラメータスニッフィングが原因であるかどうかを確認することを決定しました。したがって、プロシージャを書き直し、パラメータの盗聴を無効にするために、受信パラメータを変数に設定しました。実証済みのアプローチ。 Bam、クエリ時間が改善されました(1秒未満)。クエリプランを見ると、元のバージョンでは使用されていなかったインデックスに改善が見られました。

誤検知がないことを確認するためだけに、元のプロシージャでdbcc freeproccacheを実行し、再実行して改善された結果が同じかどうかを確認しました。しかし、驚いたことに、元のprocはまだ低速でした。まだ遅いWITH RECOMPILEを使用して再試行しました(プロシージャの呼び出しと、プロシージャ自体の内部で再コンパイルを試みました)。サーバーも再起動しました(明らかに開発ボックス)。

だから、私の質問はこれです...空のプランキャッシュで同じ低速​​のクエリを取得したときにパラメータースニッフィングのせいにすることができます... snifするパラメーターはありませんか?

代わりに、プランキャッシュに関連しないテーブル統計の影響を受けますか?そしてもしそうなら、なぜ入力パラメータを変数に設定すると役立つのですか?

さらなるテストで、プロシージャの内部にOPTION(OPTIMIZE FOR UNKNOWN)を挿入することもわかりました[〜#〜] did [〜#〜]期待される改善された計画を取得します。

では、私より賢い人がいますが、この種の結果を生み出すために舞台裏で何が起こっているのかについて、いくつかの手がかりを与えることができますか?

別の注記では、低速プランも理由GoodEnoughPlanFoundで早期に中止されますが、高速プランは実際のプランに早期中止の理由がありません。

要約すれば

  • 入力パラメーターから変数を作成する(1秒)
  • 再コンパイルあり(30秒以上)
  • dbcc freeproccache(30秒以上)
  • オプション(英国向けに最適化)(1秒)

UPDATE:

スロー実行プランはこちらをご覧ください: https://www.dropbox.com/s/cmx2lrsea8q8mr6/plan_slow.xml

ここで高速実行プランを参照してください: https://www.dropbox.com/s/b28x6a01w7dxsed/plan_fast.xml

注:テーブル、スキーマ、オブジェクト名はセキュリティ上の理由から変更されました。

41
RThomas

クエリは

_SELECT SUM(Amount) AS SummaryTotal
FROM   PDetail WITH(NOLOCK)
WHERE  ClientID = @merchid
       AND PostedDate BETWEEN @datebegin AND @dateend 
_

テーブルには103,129,000行が含まれています。

高速プランは、日付に残余述語を使用してClientIdで検索しますが、Amountを取得するには96回の検索を実行する必要があります。計画の_<ParameterList>_セクションは次のとおりです。

_        <ParameterList>
          <ColumnReference Column="@dateend" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@datebegin" 
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@merchid" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>
_

スロープランは日付でルックアップし、ClientIdの残りの述部を評価して量を取得するためのルックアップがあります(推定1と実際の7,388,383)。 _<ParameterList>_セクションは

_        <ParameterList>
          <ColumnReference Column="@EndDate" 
                           ParameterCompiledValue="'2013-02-01 23:59:00.000'" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@BeginDate" 
                           ParameterCompiledValue="'2013-01-01 00:00:00.000'"               
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@ClientID" 
                           ParameterCompiledValue="(78155)" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>
_

この2番目のケースでは、ParameterCompiledValuenot空ではありません。 SQL Serverは、クエリで使用される値を正常に探知しました。

"SQL Server 2005 Practical Troubleshooting" には、ローカル変数の使用についての記述があります

ローカル変数を使用してパラメータスニッフィングを無効にすることはかなり一般的なトリックですが、OPTION (RECOMPILE)およびOPTION (OPTIMIZE FOR)ヒント...は一般に、よりエレガントで、少しリスクの少ないソリューションです


注意

SQL Server 2005では、ステートメントレベルのコンパイルにより、ストアドプロシージャの個々のステートメントのコンパイルを、クエリの最初の実行の直前まで延期できます。そのときまでに、ローカル変数の値がわかります。理論的には、SQL Serverはこれを利用して、パラメータを盗聴するのと同じ方法でローカル変数値を盗聴できます。ただし、SQL Server 7.0およびSQL Server 2000+では、ローカル変数を使用してパラメータスニッフィングを無効にすることが一般的であったため、SQL Server 2005ではローカル変数のスニッフィングは有効になりませんでした。将来のSQL Serverリリースでは有効になる可能性がありますが、必要に応じて、この章で説明する他のオプションのいずれかを使用する理由。


この終わりの簡単なテストから、上記の動作は2008年と2012年でも同じであり、変数は据え置きコンパイル用にスニッフィングされませんが、明示的な_OPTION RECOMPILE_ヒ​​ントが使用される場合のみです。

_DECLARE @N INT = 0

CREATE TABLE #T ( I INT );

/*Reference to #T means this statement is subject to deferred compile*/
SELECT *
FROM   master..spt_values
WHERE  number = @N
       AND EXISTS(SELECT COUNT(*) FROM #T)

SELECT *
FROM   master..spt_values
WHERE  number = @N
OPTION (RECOMPILE)

DROP TABLE #T 
_

遅延コンパイルにもかかわらず、変数はスニッフィングされず、推定行数は不正確です

Estimates vs Actual

したがって、遅い計画はパラメーター化されたバージョンのクエリに関連していると思います。

ParameterCompiledValueはすべてのパラメーターのParameterRuntimeValueに等しいため、これは典型的なパラメータースニッフィングではありません(あるプランの値のセットに対してプランがコンパイルされ、別の値のセットに対して実行されます)。

問題は、正しいパラメーター値用にコンパイルされた計画が不適切であることです。

herehere と記載されている日付の昇順で問題が発生している可能性があります。 1億行のテーブルの場合、SQL Serverが統計を自動的に更新する前に、2,000万を挿入(または変更)する必要があります。前回更新されたときはゼロ行がクエリの日付範囲と一致したようですが、現在は700万行が一致しています。

より頻繁な統計更新をスケジュールし、トレースフラグ_2389 - 90_を検討するか、_OPTIMIZE FOR UKNOWN_を使用して、datetime列で現在誤解を招く統計を使用するのではなく、推測にフォールバックすることができます。

これは、SQL Serverの次のバージョン(2012以降)では必要ない場合があります。 関連するConnectアイテム には、興味深い応答が含まれます

2012年8月28日午後1時35分にマイクロソフトによって投稿されました
基本的にこれを修正する次のメジャーリリースで、カーディナリティの見積もりを強化しました。プレビューが公開されたら、詳細にご期待ください。エリック

この2014年の改善点は、記事の終わりに向かってBenjamin Nevarezが見ています。

新しいSQL Server Cardinality Estimatorの概要

この場合、新しいカーディナリティエスティメータはフォールバックし、1行の推定値ではなく平均密度を使用するようです。

2014年のカーディナリティエスティメーターと昇順の主要な問題に関する追加の詳細は次のとおりです。

SQL Server 2014の新機能–パート2 –新しい基数の見積もり

43
Martin Smith

だから、私の質問はこれです...空のプランキャッシュで同じ遅いクエリを取得したときにパラメータースニッフィングのせいにすることができます...スニッフィングするパラメーターはありませんか?

SQL Serverがパラメーター値を含むクエリをコンパイルするとき、カーディナリティ(行数)の推定のために、これらのパラメーターの特定の値 sniffs をクエリします。あなたの場合、_@BeginDate_、_@EndDate_、_@ClientID_の特定の値は、実行プランを選択するときに使用されます。パラメータスニッフィングの詳細については、 こちら および こちら をご覧ください。上記の質問は、コンセプトが現在不完全に理解されていると思うので、これらのバックグラウンドリンクを提供しています。プランがコンパイルされるときに、常に傍受するパラメータ値があります。

とにかく、マーティン・スミスが指摘したように、ここではパラメーターのスニッフィングは問題ではないので、それは要点のすべてです。遅いクエリがコンパイルされた時点で、統計は_@BeginDate_および_@EndDate_のスニッフィングされた値の行がないことを示していました:

Slow plan sniffed values

スニッフィングされた値は非常に最近のものであり、マーティンが言及している 昇順キーの問題 を示唆しています。日付のインデックスシークは単一行のみを返すと推定されるため、オプティマイザはClientIDの述語を残差としてキールックアップ演算子にプッシュするプランを選択します。

単一行の見積もりは、オプティマイザがより適切なプランの検索を停止し、Good Enough Plan Foundメッセージを返す理由でもあります。単一行の見積もりを使用した低速プランの見積もり合計コストは、わずか0.013136コスト単位であるため、より良いものを見つけようとしても意味がありません。もちろん、シークは実際には1行ではなく7,388,383行を返すため、同じ数のキールックアップが発生します。

統計は、最新の状態を維持するためにトリッキーになる可能性があり、大きなテーブルで役立ちます。パーティション分割では、その点で独自の 課題 が導入されます。私はトレースフラグ2389と2390で特に成功していませんが、それらをテストすることができます。 SQL Serverの最近のビルド(R2 SP1以降)では、 動的統計更新 を利用できますが、これは パーティションごとです統計の更新 はまだ実装されていません。それまでの間、このテーブルに大幅な変更を加えた場合は、統計の手動更新をスケジュールすることをお勧めします。

この特定のクエリについて、高速クエリプランのコンパイル中にオプティマイザによって提案されたインデックスを実装することを考えます。

_/*
The Query Processor estimates that implementing the following index could improve
the query cost by 98.8091%.

WARNING: This is only an estimate, and the Query Processor is making this 
recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide impact,
including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/
CREATE NONCLUSTERED INDEX [<Name of Missing Index>]
ON [dbo].[PDetail] ([ClientID],[PostedDate])
INCLUDE ([Amount]);
_

インデックスはON PartitionSchemeName (PostedDate) clauseを使用してパーティションにアラインする必要がありますが、ポイントは明らかに最適なデータアクセスパスを提供することで、オプティマイザが_OPTIMIZE FOR UNKNOWN_ヒ​​ントやローカル変数の使用など、昔ながらの回避策。

改善されたインデックスにより、Amount列を取得するためのキールックアップが削除され、クエリプロセッサは動的パーティションの削除を実行し、シークを使用して特定のClientIDおよび日付範囲を見つけることができます。

29
Paul White 9

ストアドプロシージャが遅くなるまったく同じ問題があり、OPTIMIZE FOR UNKNOWNおよびRECOMPILEクエリヒントにより、速度の低下を解決し、実行時間を短縮しました。ただし、次の2つの方法は、ストアドプロシージャの速度には影響しませんでした。(i)キャッシュをクリアする(ii)WITH RECOMPILEを使用する。だから、あなたが言ったように、それは実際にはパラメータの盗聴ではなかった。

トレースフラグ2389と2390も役に立ちませんでした。統計情報を更新するだけです(EXEC sp_updatestats)私のためにやった。

0
aali