web-dev-qa-db-ja.com

SQL Serverは、場合によっては最適ではない実行プランをキャッシュし、その後のすべてのクエリにそれを使用しました

私たちのアプリケーションはSQL Server 2014を使用しており、プランキャッシュに関連する問題が発生しました。

パラメータ化されたクエリがあり、その実行プランはパラメータ値に依存します。サーバーは、場合によっては最適ではない実行プランをキャッシュし、その後のクエリすべてにそれを使用します。

詳細:

次の列で構成されるテーブルがあります。

_(
 [Revision] [bigint] IDENTITY(1,1) NOT NULL,
 [UserId] [uniqueidentifier] NOT NULL,
 ...A WHOLE LOT OF OTHER COLUMNS...
)
_

これらの2つの列の意味はかなり明確です。UserIdはレコードが属するユーザーのIDで、Revisionはレコードの自動インクリメントインデックスです。他の列は重要ではありませんが、存在して実行プランに影響を与えます。

テーブルには〜40.000.000行と〜200.000の個別のUserId値が含まれているため、各ユーザーの平均レコード数は200です。行は更新されません。データの変更にはINSERTとDELETEのみを使用します。

アプリケーションは、このテーブルに対して次のクエリを実行します。

_SELECT * FROM SampleTable WHERE Revision > {someRevision} AND UserId = {someId}
_

テーブルには2つのインデックスがあります。

  1. クラスター化インデックス:_Revision asc_
  2. 非クラスター化インデックス:_UserId asc, Revision asc_

このクエリを手動で実行すると、実行プランがsomeRevisionの値に依存していることがわかります。

  • 現在のリビジョンの最大値に比較的近い場合、サーバーは_Clustered Index Seek_とともに_Seek Predicate: Revision > someRevision_を使用します

  • 閉じていない場合、サーバーはIndex Seek (NonClustered) + Key Lookup (Clustered)を_Seek Predicate: UserId = someId AND Revision > someRevision_とともに使用します。

私たちのアプリケーションはLinq-To-Sqlを使用し、パラメーター化されたクエリを生成します。これらは次のようになります。

_exec sp_executesql N'SELECT * FROM [SampleTable] AS [t0]
WHERE ([t0].[Revision] > @p0) AND ([t0].[UserId] = @p1)',N'@p0 bigint,@p1 
uniqueidentifier',@p0=1234,@p1='bc38dd12-238c-41a2-9dea-bb12ce105e6d'
_

私は_dm_exec_cached_plans_、_dm_exec_sql_text_、_dm_exec_query_plan_を使用し、サーバーがこのクエリの単一のプランをキャッシュに入れることを理解しました。したがって、対応する値がRevisionのクエリが最初に来た場合、_Clustered Index Seek_を使用するプランはプランキャッシュに格納され、その後のすべてのクエリで使用されます。

これは、2番目の計画(Index Seek (NonClustered) + Key Lookup (Clustered))を使用して実行する必要があるクエリの論理読み取り(x10000)の過剰な数と許容できない実行時間につながります。

また、サーバーが計画間で切り替わるしきい値(転換点)が統計に依存していることにも気付きました。古くなっている場合、サーバーはRevision指定された値より大きい。

さらに、類似のユースケースを持つ類似のテーブルの大規模なセットがあり、それらすべてに同じ問題があります。

この問題を解決するにはどうすればよいですか?

OPTION (RECOMPILE)を使用することもできますが、これはLinq-To-Sqlでは簡単ではありませんが、パフォーマンスの点では本当に最適に見えません。

また、_sp_create_plan_guide_を使用するか、Linq-To-SqlをさらにハックしてWITH (INDEX(...))句を使用して2番目の計画を強制することもできますが、前述のように、同じコアを持つテーブルがたくさんあります構造なので、この方法は多くの手作業のように見えます。

一般的に、私の質問:

SQL Serverは、キャッシュに格納されているプラ​​ンが特定のパラメーターに最適ではないことを理解し、それを使用しないのですか?

最適な実行プランがパラメーターに依存している場合、パラメーター化されたクエリを処理するためのいくつかのベストプラクティスはありますか?

5
RomanG

これはパラメータースニッフィングと呼ばれ、Erland Sommarskogの叙事詩の記事 アプリで低速、SSMSで高速 で広くカバーされています。

ここでも正義を始めることはできませんが、サンプルソリューションには次のものがあります。

  • OPTION(RECOMPILE)-プランのコンパイルでCPU使用率が増加し、さらにクエリ実行の履歴メトリックが失われますが、パラメーターのセットごとに一意のプランを構築できます(ただし、カーディナリティの推定問題の場合は、次善のプランになる可能性があります) )
  • 特定の値の最適化-データをよく理解している場合は、OPTIMIZE FORヒントを使用して、ユーザーが渡したものに関係なく、特定のパラメーター値に対して計画が常に構築されるようにすることができます。これは、技術的負債を作成するようなものです。データスキューが変化した場合、より適切な計画を得るために、コードを再検討する必要がある場合があります。
  • インデックスヒントの使用-クエリオプティマイザーを使用しているだけでなく、そのインデックスが消えた場合、クエリは単に失敗するため、通常、特定の値を最適化するよりも悪い方法です。 SQL Serverは、クエリに代替インデックスを使用しようとしません。
  • プランガイド-ただし、クエリについて何か変更があると、たとえ1文字でも、プランガイドは一致しなくなります。
  • クエリとインデックスのチューニングの組み合わせ-開発者に*(すべてのフィールド)の選択を避け、本当に必要なフィールドのみを取得します。次に、一致するカバリングインデックスを作成すると、すべてのパラメーターに対して適切に機能する単一のクエリプランが得られます。

先に進んで取り組む Erlandの優れた記事 -今日の配当金を支払うだけでなく、この問題を何度も解決するため、キャリアを通じて成果を上げ続けるでしょう。今日のクエリで適切に機能するソリューションは、明日他のクエリで使用するソリューションとは大きく異なる可能性があります。

7
Brent Ozar