次のクエリがあります。
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つのインデックスを定義します:
EventId
主キー列のクラスター化インデックスDateCreated
列の非クラスター化インデックス読み取り その他の投稿 キー検索を排除する方法について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
しかし、これは役に立たず、実際の実行計画は同じです。キールックアップを見ると、出力は実際には新しく追加された非クラスター化インデックスに含まれています。
私の質問は、なぜそれがまだインデックススキャン/シークの代わりにkey lookup
をしているのですか?
[〜#〜]更新[〜#〜]
コメントのいくつかの提案に従って、新しく作成された非クラスター化インデックスを削除し、代わりにDateCreated
の列を含むSELECT
列に非クラスター化インデックスを再作成しました。
現在、実行計画は次のとおりです。
また、クエリの実行時間も1分以上から数秒に短縮されました(このテーブルには1800万以上の行があります)。
これは、非クラスター化インデックスのORDER BY
が原因でキーの検索が行われたことを意味しますか?
私の質問は、なぜそれがまだインデックススキャン/シークの代わりにキールックアップをしているのですか?
クエリは、結果を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が示唆するように 。
列DateCreated
をインデックス_LocationId_FirstTrigger
_のキー列に移動する必要があります。
次に、LocationId
とFirstTrigger
のシークと、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は一致するすべての行を読み取り、それらを並べ替えて、どの行を返すかを知る必要があります。