SQL Server 2016 未使用 2012 年使用的索引

Sax*_*man 5 sql-server index-tuning nonclustered-index sql-server-2016

2016年计划在这里

2012年计划在这里

我有几乎相同的表,行数差异很小。一个在 2012 年,另一个在 2016 年。索引是相同的。这些 VM 与升级的 OS 和 SQL Server 版本处于完全相同的环境中。相同数量的 vcores,相同的内存,相同的服务器设置(最大并行度 = 8,并行度的成本阈值 = 30)。

这个简单的单条记录查询使用单列进行过滤和单列返回。where 过滤器中的列是索引中的唯一列。

2016 版有 8254356 行
2012 版有 8254427 行

它们是相同的查询。2016 缺少索引并无缘无故地进行全表扫描。2012 在索引扫描后对表进行 RID 查找(堆)。

WITH (index = CONTACT_RC_NUI1)在2016服务器上试过,成本从991跳到1889。2012的成本是29。

我尝试添加AND 1 = (SELECT 1),但没有任何区别。
我尝试通过使用删除参数嗅探作为可能的问题OPTION (RECOMPILE),但没有任何区别。

DBA 在恢复数据库后运行索引重建。两台服务器都有最近的索引统计更新(我们运行 Ola 的索引更新脚本)。并且可以肯定的是,我在 2016 年重建了索引,这对 2016 年的解释计划没有影响。

我在查询中添加了以下提示...

select address1_stateorprovince 
from dla.dcrm.CONTACT_RC WITH (index = CONTACT_RC_NUI1)
where wv_partyid = 343083;
Run Code Online (Sandbox Code Playgroud)

这导致成本从 991 增加到 1889,即使它显示与 2012 年的解释计划几乎相同(2016 年只是添加了并行性(收集流))。

2016 年似乎正在做的是仅花费 1% 的索引成本,但 RID 查找是 99%。2012 年,这种情况发生了逆转。看来 2016 年使用索引扫描所有条目,然后查找表中的每个 RID?那会是真的吗?我认为 2016 年优化器一直在吸一些非常强大的东西。

  • wv_party_idnvarchar(100)

Mik*_*son 9

您会看到不同的计划,因为从 SQL Server 2014 开始,SQL Server 中有一个新的基数估计器。然后他们为新的 CE向 SQL Server 2016添加了一些新功能

首先是一些测试数据来重现你所看到的。

create table dbo.T(C1 char(10) default '', C2 varchar(11));

go

insert into dbo.T(C2)
select top(800000) row_number() over(order by (select null))
from sys.columns as c1, sys.columns as c2, sys.columns as c3

go

create index IX_T_C2 on dbo.T(C2)
Run Code Online (Sandbox Code Playgroud)

以及将为您生成两个不同计划的查询,以便您可以在相同版本的 SQL Server 中比较它们。

-- Table scan version
select C1
from dbo.T with (index = 0)
where C2 = 100000
option (maxdop 1);

-- Index Scan version
select C1
from dbo.T with (index = IX_T_C2)
where C2 = 100000
option (maxdop 1);
Run Code Online (Sandbox Code Playgroud)

SQL Server 2012 中的表扫描版本扫描所有行并返回一个。不出意外。

SQL Server 2012 的索引扫描版本扫描索引中的所有行并返回一行。有一些东西需要进一步查看,但现在您应该额外查看 Index Scan 运算符的 Estimated Number of Rows。

在此处输入图片说明

SQL Server 2016 的 Table Scan 版本与 2012 的版本没有什么不同。它扫描所有行,返回一行。

Index Scan 版本看起来与 2012 年相同,但成本要高得多,这是因为 Estimated Number of Rows 比 2012 年高得多

在此处输入图片说明

所以 SQL Server 现在认为它必须执行 80000 RID Lookup 才能返回 1 行,这就是为什么它选择 SQL Server 2016 中带有新基数估计器的表扫描。

新估计器看到谓词where CONVERT_IMPLICIT(int,C2) = 100000并放弃。它对等式谓词使用10% 选择性的标准猜测,其中 800,000 行的 10% = 80,000。原始估计器使用更复杂的逻辑来生成一行的非猜测(准确!)估计。

现在来解决索引扫描的问题。那可能不是您想要的。您希望 SQL Server 执行索引查找以查找您要查找的行。目前 SQL Server 不能这样做,因为 where 子句中有类型不匹配,并且在查询计划中确实收到了有关它的警告。解决这个问题,您应该会在两个版本的 SQL Server 中看到一个包含索引查找和 RID 查找的计划。

另请注意,执行计划中的成本百分比始终基于优化器估计,而不是实际运行时信息。


Eri*_*ing 5

SQL Server 根据成本计算做出计划选择。

过于简单化:优化器将选择成本较低的计划。头在这里,如果你真的热衷于学习更多关于位。没有人比保罗怀特更好。

这并不意味着它探索了所有可能的计划,或者成本计算是正确的。它基于几十年前的硬件。

有一个临界点,优化器在执行查找(键或 RID)和扫描聚集索引(或选择索引交集)以满足查询之间进行选择。

如果要对此进行测试,请在 2016 框上运行查询,并强制使用非聚集索引。

SELECT [columns]
FROM dbo.YourTable WITH (INDEX = ix_YourIndex)
Run Code Online (Sandbox Code Playgroud)

将其成本与仅扫描聚集索引的成本进行比较。

作为旁注,您的查询是简单的参数化。如果向 where 子句添加额外的谓词,例如AND 1 = (SELECT 1);,您可能会看到不同的计划选择。

有时通过参数化(简单或其他方式),以前运行和缓存的查询可以选择一个对另一个查询不起作用的计划。这称为参数嗅探。

如果您想了解有关参数嗅探的更多信息,请访问此处。Erland 写了一篇关于它的不错的博客/书籍/圣经。

如果您想从优化器中取消该选择,请将您选择的任何列添加INCLUDE为非聚集索引中的 -deded 列。这样就不需要进行查找来获取它们。

但我只看你的查询计划的图片。优化器一直在进行调整和更改。最重要的是,您在具有不同数据和不同硬件的不同服务器上。如果您想要更具体的答案,请发布更具体的信息。