Covering Index Changes Execution Plan 但未使用

Fle*_*tch 4 performance index sql-server execution-plan sql-server-2016 query-performance

我有以下偶尔运行缓慢的查询:

SELECT C.CustomerID 
FROM dbo.Customers C WITH (NOLOCK)
WHERE C.Forename = @Forename
    AND C.Surname = @Surname
OPTION (RECOMPILE)
Run Code Online (Sandbox Code Playgroud)

CustomerID 是Customers 表上的主键。客户表还有以下两个非聚集索引:

CREATE NONCLUSTERED INDEX idx_Forename ON Customers (Forename ASC)
CREATE NONCLUSTERED INDEX idx_Surname ON Customers (Surname ASC)
Run Code Online (Sandbox Code Playgroud)

当我使用输入的姓氏和名字运行查询时,查询优化器使用索引“idx_Surname”,如以下执行计划所示:

不覆盖索引的执行计划

对于此特定搜索,此查询需要两分钟多的时间才能完成,并且未找到任何结果。对于输入的值,@Forename 在 Customers 表中没有匹配项,而 @Surname 匹配 31,162 条记录。当我只按 @surname 搜索时,31,162 条记录会在不到一秒钟的时间内返回,并采用以下计划:

执行计划仅姓氏搜索

为了优化包含 Forename 和 Surname 的搜索的查询,我添加了以下覆盖索引:

CREATE NONCLUSTERED INDEX idx_Surname_Covering ON dbo.Customers (Surname) INCLUDE (Forename)
Run Code Online (Sandbox Code Playgroud)

带有 Forename 和 Surname 的查询将在不到一秒的时间内返回。但是,实际执行计划中并没有使用覆盖索引:

带覆盖索引的执行计划

所以,

  1. 是否需要覆盖索引或有更好的方法来提高性能和
  2. 为什么额外的覆盖索引会导致实际执行计划中的索引从idx_Forename 变为idx_Surname?

ps 上面的查询是一个孤立的例子,在使用时,可以搜索姓氏或名字,或者两者都可以搜索,并且客户表还包括其他具有自己索引的可搜索列。这个细节被认为与问题无关,所以我没有包括它。

Ran*_*gen 5

1)是否需要覆盖索引或者有更好的方法来提高性能

最佳指数

最好的索引是访问表的查询的覆盖范围最广、选择性最强的索引。

以您的表为例,您有 50000 行,其中 firstname = John ,但只有一个 last name = 'McClane',您应该使用 John 作为第一个键值还是 McClane 创建索引?

回答:

这取决于...如果您一直在搜索 John Mcclane,那么首先将姓氏编入索引是一个开放和封闭的案例。但是,如果还有搜索 Constanthin Smith 的查询呢?你可以有 5000 多个史密斯,但只有五个康斯坦丁。

因此,这取决于您的查询和您正在寻找的内容,它们的执行量,......

如果您的查询将始终同时搜索名字和姓氏,那么选择更具选择性的作为第一个键列的简单情况。请记住,读取性能的提升应该大于写入性能的下降。

当然,没有人会限制您创建两个索引,一个是 (firstname,lastname),另一个是 (lastname,firstname)。

(您的更新/插入/删除语句可能)。

不考虑过滤索引等,您的示例的最佳索引是:

CREATE NONCLUSTERED INDEX idx_Forename_Surname ON dbo.Customers (Forename,Surname)
Run Code Online (Sandbox Code Playgroud)

2)为什么额外的覆盖索引会导致实际执行计划中的索引从idx_Forename变为idx_Surname?

我不认为这仅仅是因为索引,而是因为索引创建后创建的统计信息。

尽管这些统计数据与 idx_Surname 中的统计数据相同,但我的猜测是它们具有更大的采样率 (100),因为它们是使用“全扫描”创建的。

如果索引创建的统计数据发生自动更新统计数据idx_Surname,则它们可能具有较小的采样率,从而导致错误的估计(例如 1% 采样率)。

您可以尝试删除idx_Surname_Covering索引及其统计信息并dbo.Customers以 100% 采样率(全扫描)更新统计信息以测试该理论。

UPDATE STATISTICS dbo.Customers WITH FULLSCAN
Run Code Online (Sandbox Code Playgroud)

这有望改变您使用更好搜索的计划。

如果这就是您的查询更改的原因,并且在维护窗口上使用全扫描更新统计数据不是一个可行的选择,您可以更改采样率