消除执行计划中的键查找

Mic*_*ael 7 performance sql-server execution-plan sql-server-2012 query-performance

我有以下查询:

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
Run Code Online (Sandbox Code Playgroud)

Watchdog 表定义了 2 个 indecies:

  1. EventId主键列上的聚集索引
  2. 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
Run Code Online (Sandbox Code Playgroud)

但是,这没有帮助,实际执行计划是一样的。如果我查看键查找,输出实际上包含在新添加的非聚集索引中。

在此处输入图片说明

我的问题是,为什么它仍然在做key lookup而不是索引扫描/搜索?


更新

按照评论中的一些建议,我删除了新创建的非聚集索引,而是在DateCreated包含来自SELECT.

现在执行计划如下:

在此处输入图片说明

查询执行时间也从 1+ 分钟减少到几秒(该表有 18+ 百万行)。

这是否意味着由于ORDER BY非聚集索引而完成了键查找?

Jos*_*ell 8

我的问题是,为什么它仍在执行键查找而不是索引扫描/搜索?

查询指定结果应按 排序DateCreated。由于您已经有一个非聚集索引DateCreated,优化器决定执行键查找的成本低于对所有数据进行排序的成本DateCreated

这是否意味着由于非聚集索引上的 ORDER BY 而完成了键查找?

基本上,是的。据估计,以所需的顺序读取数据并通过键查找获取任何其他字段会更便宜*,而不是从单个索引中读取所有字段,然后按DateCreated.

您可以通过比较两者之间的估计成本来确认这一点

  • 原始查询(带有原始索引),以及
  • 带有索引提示的原始查询

索引提示应该是这样的FROM

FROM [dbo].[Watchdog] WITH (INDEX (LocationId_FirstTrigger))
Run Code Online (Sandbox Code Playgroud)

这应该会生成一个没有键查找的计划(因为LocationId_FirstTrigger覆盖了该查询)和一个Sort运算符。我预计“估计成本”会更高,因此选择了另一个计划。


* 在这里解释优化器的选择:

TOP (1)您的查询手段优化设置一个排的目标,这意味着该计划向快速生成一列面向。优化器期望从索引扫描中LocationId非常快速地找到与您的谓词匹配的一行,因为它假定值是均匀分布的。这在现实中可能是也可能不是。一个成本键查找下面的索引扫描是非常小的。

因此,对于优化器来说,扫描 + 查找选项看起来比使用LocationId_FirstTrigger和排序查找匹配更便宜。您可以通过添加OPTION (QUERYTRACEON 4138)提示来关闭查询的行目标逻辑作为测试。您可能会发现优化器然后选择LocationId_FirstTrigger没有索引提示的索引。

尽管如此,最好的选择还是按照 Mikael Eriksson 的建议修改您的索引。


Mik*_*son 7

您应该将该列移动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]
Run Code Online (Sandbox Code Playgroud)

如果索引不是按DateCreatedSQL Server排序的,则必须读取所有匹配的行并对它们进行排序以知道要返回的行。