web-dev-qa-db-ja.com

実行計画でのキー検索を排除

次のクエリがあります。

DECLARE @p__linq__0 UNIQUEIDENTIFIER

SET @p__linq__0 = '... some guid ...'

SELECT TOP 1
    [EventId] AS [EventId],       
    [DateCreated] AS [DateCreated],       
    [LocationId] AS [LocationId],       
    [SourceName] AS [SourceName],       
    [SourceState] AS [SourceState],       
    [Priority] AS [Priority],       
    [EventDescription] AS [EventDescription],       
    [FirstTrigger] AS [FirstTrigger]
FROM [dbo].[Watchdog]
WHERE 
    [LocationId] = @p__linq__0
    AND 
    [FirstTrigger] = 1
ORDER BY [DateCreated] DESC

Watchdogテーブルは2つのインデックスを定義します:

  1. EventId主キー列のクラスター化インデックス
  2. DateCreated列の非クラスター化インデックス

これは、クエリの実際の実行プランです: enter image description here

読み取り その他の投稿 キー検索を排除する方法についてSELECTのすべての列を含む別の非クラスター化インデックスを追加しました

CREATE NONCLUSTERED INDEX [LocationId_FirstTrigger] ON [dbo].[Watchdog]
(
    [LocationId] ASC,
    [FirstTrigger] ASC
)
INCLUDE (   [EventId],
    [DateCreated],
    [SourceName],
    [SourceState],
    [Priority],
    [EventDescription]) WITH (STATISTICS_NORECOMPUTE = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [PRIMARY]
GO

しかし、これは役に立たず、実際の実行計画は同じです。キールックアップを見ると、出力は実際には新しく追加された非クラスター化インデックスに含まれています。

enter image description here

私の質問は、なぜそれがまだインデックススキャン/シークの代わりにkey lookupをしているのですか?


[〜#〜]更新[〜#〜]

コメントのいくつかの提案に従って、新しく作成された非クラスター化インデックスを削除し、代わりにDateCreatedの列を含むSELECT列に非クラスター化インデックスを再作成しました。

現在、実行計画は次のとおりです。

enter image description here

また、クエリの実行時間も1分以上から数秒に短縮されました(このテーブルには1800万以上の行があります)。

これは、非クラスター化インデックスのORDER BYが原因でキーの検索が行われたことを意味しますか?

6
Michael

私の質問は、なぜそれがまだインデックススキャン/シークの代わりにキールックアップをしているのですか?

クエリは、結果をDateCreatedで並べ替えることを指定しています。 DateCreatedに非クラスター化インデックスが既にあるため、オプティマイザーは、キールックアップを実行するコストが、すべてのデータをDateCreatedでソートするよりも低いと判断しました。

これは、非クラスター化インデックスのORDER BYが原因でキーの検索が行われたことを意味しますか?

本質的に、はい。単一のインデックスからすべてのフィールドを読み取ってDateCreatedで並べ替えるよりも、必要な順序でデータを読み取り、キールックアップを通じて追加のフィールドを取得する方がコストが低いと推定されました*。

これを確認するには、推定コストを

  • 元のクエリ(元のインデックスを使用)、および
  • インデックスヒントを含む元のクエリ

インデックスのヒントは、FROM行では次のようになります。

_FROM [dbo].[Watchdog] WITH (INDEX (LocationId_FirstTrigger))
_

これにより、キールックアップのないプラン(_LocationId_FirstTrigger_がそのクエリをカバーするため)とSort演算子が生成されます。 「推定コスト」が高くなると思いますので、他の案を選びました。


*オプティマイザの選択をここで説明するには:

クエリのTOP (1)は、オプティマイザが 行の目標 を設定することを意味します。つまり、計画は1つの行をすばやく生成することを目的としています。オプティマイザーは、値が均一に分散されていると想定しているため、LocationId述語に一致するIndex Scanから1行を非常に迅速に検出することを期待しています。これは実際には当てはまらない場合があります。 Index Scanに続く1つのKey Lookupのコストはかなり小さいです。

したがって、スキャン+ルックアップオプションは、_LocationId_FirstTrigger_を使用して一致を検索し、並べ替えるよりもオプティマイザにとって安価に見えます。 OPTION (QUERYTRACEON 4138)ヒントを追加することにより、クエリの行目標ロジックをテストとしてオフにすることができます。オプティマイザは、インデックスヒントなしで_LocationId_FirstTrigger_インデックスを選択する可能性があります。

それでも、最良の代替案はインデックスを変更することです Mikael Erikssonが示唆するように

7
Josh Darnell

DateCreatedをインデックス_LocationId_FirstTrigger_のキー列に移動する必要があります。

次に、LocationIdFirstTriggerのシークと、DateCreatedの最大値を持つ行を見つけるためのインデックスの逆方向スキャンをselect top(1) ... order by DateCreated descに従って取得します。

_CREATE NONCLUSTERED INDEX [LocationId_FirstTrigger] ON [dbo].[Watchdog]
(
    [LocationId] ASC,
    [FirstTrigger] ASC,
    [DateCreated]
)
INCLUDE (   
    [EventId],
    [SourceName],
    [SourceState],
    [Priority],
    [EventDescription]) WITH (STATISTICS_NORECOMPUTE = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [PRIMARY]
_

インデックスがDateCreatedによって順序付けられていない場合、SQL Serverは一致するすべての行を読み取り、それらを並べ替えて、どの行を返すかを知る必要があります。

6
Mikael Eriksson