パラメータースニッフィングvs変数vs再コンパイルvs未知の最適化


40

そのため、今朝(30秒+実行時間)に問題を引き起こす長時間実行中のprocがありました。パラメータスニッフィングが原因かどうかを確認することにしました。そのため、パラメータスニッフィングを無効にするために、procを書き直し、受信パラメータを変数に設定しました。試された/真のアプローチ。バム、クエリ時間が改善されました(1秒未満)。クエリプランを見ると、元のインデックスでは使用されていなかった改善が見つかりました。

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

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

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

さらなるテストでは、proc DIDの内部にOPTION(OPTIMIZE FOR UNKNOWN)を挿入すると、予想される改善された計画が得られることもわかりました。

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

別の注意事項として、遅いプランもGoodEnoughPlanFound理由により早期に中止されますが、速いプランには実際の計画には早期中止の理由がありません。

要約すれば

  • 受信パラメータから変数を作成する(1秒)
  • 再コンパイル(30秒以上)
  • dbcc freeproccache(30秒以上)
  • オプション(イギリス向けに最適化)(1秒)

更新:

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

こちらの高速実行プランをご覧くださいhttps : //www.dropbox.com/s/b28x6a01w7dxsed/plan_fast.xml

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

回答:


43

クエリは

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

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

高速プランは、日付の残存述部を使用してClientIdでルックアップしますが、を取得するには96回のルックアップを行う必要がありますAmount<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番目のケースでParameterCompiledValueは、は空ではありません。SQL Serverは、クエリで使用される値をスニッフィングしました。

著書「SQL Server 2005の実用的なトラブルシューティング」、これはローカル変数を使用してについて言いたいことがあります

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


注意

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


この目的のための簡単なテストから、上記の動作は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 

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

見積もりと実際

したがって、スロープランはクエリのパラメーター化されたバージョンに関連していると想定しています。

これParameterCompiledValueParameterRuntimeValueすべてのパラメーターのに等しいため、これは一般的なパラメータースニッフィングではありません(1つの値セットに対して計画がコンパイルされ、別の値セットに対して実行されます)。

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

ここここで説明さている昇順の日付で問題が発生している可能性があります。1億行のテーブルでは、SQL Serverが自動的に統計を更新する前に、2000万行を挿入(または変更)する必要があります。最後に更新された行はクエリの日付範囲と一致していなかったようですが、現在では700万行が一致しています。

統計情報の更新をより頻繁にスケジュールし2389 - 90たり、トレースフラグを検討したりOPTIMIZE FOR UKNOWNdatetime列で現在誤解を招く統計情報を使用したりせずに推測に基づいてフォールバックすることもできます。

これは、SQL Serverの次のバージョン(2012年以降)では必要ない場合があります。関連の接続項目は、魅力的な応答が含まれています

Microsoftによる2012年8月28日午後1:35の投稿
次のメジャーリリースで、これを本質的に修正するカーディナリティ推定の強化を行いました。プレビューが公開されたら、詳細にご注目ください。エリック

この2014年の改善は、記事の終わり頃にベンジャミンネバレスによって検討されています。

新しいSQL Serverのカーディナリティ見積もりでA初見

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

2014年のカーディナリティ推定量と昇順の重要な問題に関する追加の詳細は次のとおりです。

SQL Server 2014の新機能–パート2 –新しいカーディナリティの推定


29

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

SQL Serverは、パラメーター値を含むクエリをコンパイルするときに、カーディナリティ(行数)の推定のためにこれらのパラメーターの特定の値を探ります。あなたの場合、実行計画を選択するときに@BeginDate@EndDateとの特定の値@ClientIDが使用されます。パラメータスニッフィングの詳細については、こちらこちらをご覧ください。これらの背景リンクを提供しているのは、上記の質問により、概念が現時点では不完全に理解されていると思うようになるためです。

とにかく、それはすべてポイントの横にあります。MartinSmithが指摘したように、ここではパラメータースニッフィングは問題ではないからです。遅いクエリがコンパイルされたとき、統計は@BeginDateandのスニッフィングされた値の行がないことを示しました@EndDate

遅い計画スニッフィング値

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

単一行の推定値は、オプティマイザーがより良いプランの検索を停止し、十分なプランが見つかりましたというメッセージを返す理由でもあります。単一行の見積もりを使用した低速プランの見積もり合計コストは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)句を使用してパーティションに揃える必要がありますが、ポイントは、明らかに最適なデータアクセスパスを提供することで、OPTIMIZE FOR UNKNOWNローカル変数を使用するなどのヒントや昔ながらの回避策に頼ることなく、オプティマイザーが不適切なプランの選択を回避できるようになることです。

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


2つの答えを正しいものとしてマークできたらいいのですが、追加の情報に感謝します-非常に有益です。
–RThomas

1
これを投稿してから数年が経ちました...しかし、私はあなたに知らせたいだけです。私はいまだに「完全に理解されていない」という言葉をおかしな時間で使っています。いつもくすくす笑う。
–RThomas

0

私は、ストアドプロシージャが遅くなりました正確に同じ問題を抱えていた、とOPTIMIZE FOR UNKNOWNしてRECOMPILEクエリヒントは、遅さを解消し、実行時間をスピードアップ。ただし、次の2つの方法は、ストアドプロシージャの速度に影響しませんでした。(i)キャッシュをクリアします(ii)WITH RECOMPILEを使用します。それで、あなたが言ったように、それは実際にはパラメータ探知ではありませんでした。

トレースフラグ2389および2390も役に立ちませんでした。統計(EXEC sp_updatestats)を更新するだけでそれができました。

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