web-dev-qa-db-ja.com

SQL Serverは週に1度、非効率的な計画(クラスター化インデックススキャン)に戻ります

私は非常に単純なクエリを持っています:

INSERT INTO #tmptbl
SELECT TOP 50 CommentID --this is primary key
FROM Comments WITH(NOLOCK)
WHERE UserID=@UserID
ORDER BY CommentID DESC

このテーブルに対して:

CREATE TABLE [dbo].[Comments] (
    [CommentID] int IDENTITY (1, 1) NOT NULL PRIMARY KEY,
    [CommentDate] datetime NOT NULL DEFAULT (getdate()),
    [UserID] int NULL ,
    [Body] nvarchar(max) NOT NULL,
--a couple of other int and bit cols, no indexes on them
)

UserID列に単純なインデックス(colsは含まれません)があり、すべて正常に動作し、超高速です。

しかし、5〜8日ごとに1回、アプリケーションのその部分でタイムアウトが発生します。クエリストアで調査を行ったところ、サーバーがインデックスの使用を停止し、愚かな "クラスタースキャン"に戻っていることがわかりました。一時テーブルを削除しても効果はありません。

why, Gosh, why???

これを解決するために、私はこの特定のクエリのプランキャッシュをリセットします(ここのレコードだけがこれを行う方法です)

select plan_handle FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text (qs.[sql_handle]) AS qt
where text like '%SELECT TOP 50 CommentID FROM hdComments%'
--blahblahblah skipped some code
DBCC FREEPROCCACHE (@plan_handle)

そして、再び正常に動作し始めます。

実行計画: 遅い速い

何日も頭を悩ませてきました…何かアイデアはありますか?

4
jitbit

UserIDのインデックスは、そのクエリに最適ではありません。オプティマイザはそれを使用する選択肢を残し、CommentIDによる並べ替えを追加するか、テーブルをスキャンして(逆方向に)、すでにCommentIDで並べ替えられており、where句と最上位の演算子によってその場でフィルタリングされています。クラスター化されたPK列はクラスター化されていない各列に含まれていますが、これはポインターと同じであるため、並べ替えには使用できません。

説明するような重要なクエリでこれを回避する最良の方法は、最適なインデックスを提供することです。そのため、オプティマイザは毎回それを選択する可能性が高くなります。入力した情報に基づいて、インデックスは(UserID、CommentID DESC)の複合非クラスター化インデックスである必要があります。これにより、ユーザー行への直接アクセスが可能になり、コメントIDの順序で最初の50行をスキャンして最適なままにすることができます。統計と選択性に関係なく、選択。

SQLサーバーはそれを実現するのに十分スマートです。試してみてください... HTH

3
SQLRaptor

SQLRaptorの答えがうまくいかない場合、もう1つdrastic試すことができるのは、クエリヒントFORCESEEKを使用することです。これにより、オプティマイザは常に(可能な場合)インデックススキャンの代わりにインデックスシークを行うプランを常に使用するように強制されます。

これが最初の目的ではない理由の1つは、オプティマイザが使用することを選択できるクエリプランの数を制限し、場合によっては、そのクエリヒントに使用できるプランがないことを示すことによってエラーになるためです。通常、クエリヒントは(特定のEdgeの場合を除いて)より最後の手段であるバンダイド修正ですが、DBCC FREEPROCCACHEコマンドを実行するよりも間違いなく、KumarHarshの回答からのOPTION RECOMPILEヒントを使用してクエリを常に再コンパイルするよりも、それほど簡単ではありません。

(これは、私が最近数十億のレコードを含むテーブルで遭遇した特定のシナリオにとって最良の解決策になり、オプティマイザは常にクラスタ化インデックススキャンを使用しようとしましたが、クエリにより適した非クラスタ化インデックスがあり、実際には常にシークとして高速でした。)

詳細については、MicrosoftドキュメントのFORCESEEKセクションを参照してください。 https://docs.Microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql- server-ver15

4
J.D.

これは、主キーが複合であり、1つの列が作成された日にかなり頻繁に更新される、不適切に設計されたシステムでこれまでに見たことがあります。これにより、インデックスの再構築がスケジュールされる前の夜(24時間年中無休のシステムでした)までにインデックスの断片化が発生しました。その時点で、実際のP​​Kを使用したクエリがより高速であったとしても、SQLは最適なクエリの使用を停止し、劇的にスローダウンしました。もちろん、インデックスが再構築されると、SQLは正常なクエリプランに戻りました。

これに対する回避策は、クエリにインデックスのヒントを与えることでした。これは次の方法で実行できます。

select …..
from tableA a with(index(pk_tableA)) -- any table index allowed
inner join tableB b on b.Id = a.BId
where etc etc

それは良い解決策のようには感じられませんでした-理想的には、テーブルとその使用方法を再設計したはずですが-予算。

注意(Jonathonがコメントで私に思い出させるように)インデックスはオフラインではなくオンラインで再構築されました。これは、rebuildコマンドで指定する必要があります。

以下から MSSqlTips.com

ALTER INDEX [IX_Test] ON [dbo].[Test] REBUILD WITH (ONLINE = ON);

このオプションは、次の場合は使用できません。

  • インデックスはXMLインデックスです
  • インデックスは空間インデックスです
  • インデックスはローカル一時テーブルにあります
  • インデックスがクラスター化されており、テーブルにLOBデータベース列が含まれている
  • インデックスがクラスター化されておらず、インデックス自体にLOBデータベース列が含まれている

また、Denis Rubashkinが述べているように、オンラインオプションはSQL Serverエンタープライズバージョンでのみ使用できます。

0
simon at rcl