为什么优化器会选择聚集索引 + 排序而不是非聚集索引?

McN*_*ets 11 sql-server sql-server-2012 nonclustered-index

给出下一个例子:

IF OBJECT_ID('dbo.my_table') IS NOT NULL
    DROP TABLE [dbo].[my_table];
GO

CREATE TABLE [dbo].[my_table]
(
    [id]    int IDENTITY (1,1)  NOT NULL PRIMARY KEY,
    [foo]   int                 NULL,
    [bar]   int                 NULL,
    [nki]   int                 NOT NULL
);
GO

/* Insert some random data */
INSERT INTO [dbo].[my_table] (foo, bar, nki)
SELECT TOP (100000)
    ABS(CHECKSUM(NewId())) % 14,
    ABS(CHECKSUM(NewId())) % 20,
    n = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
FROM 
    sys.all_objects AS s1 
CROSS JOIN 
    sys.all_objects AS s2
GO

CREATE UNIQUE NONCLUSTERED INDEX [IX_my_table]
    ON [dbo].[my_table] ([nki] ASC);
GO
Run Code Online (Sandbox Code Playgroud)

如果我获取按[nki](非聚集索引)排序的所有记录:

SET STATISTICS TIME ON;
SELECT id, foo, bar, nki FROM my_table ORDER BY nki;
SET STATISTICS TIME OFF;

SQL Server Execution Times: CPU time = 266 ms, elapsed time = 493 ms
Run Code Online (Sandbox Code Playgroud)

优化器选择聚集索引,然后应用排序算法。

在此处输入图片说明

Execution plan

但是如果我强制它使用非聚集索引:

SET STATISTICS TIME ON;
SELECT id, foo, bar, nki FROM my_table WITH(INDEX(IX_my_TABLE));
SET STATISTICS TIME OFF;

SQL Server Execution Times: CPU time = 311 ms, elapsed time = 188 ms
Run Code Online (Sandbox Code Playgroud)

然后它使用非聚集索引和键查找:

在此处输入图片说明

Execution plan

显然如果非聚集索引转化为覆盖索引:

CREATE UNIQUE NONCLUSTERED INDEX [IX_my_table]
    ON [dbo].[my_table] ([nki] ASC)
    INCLUDE (id, foo, bar);
GO
Run Code Online (Sandbox Code Playgroud)

然后它只使用这个索引:

SET STATISTICS TIME ON;
SELECT id, foo, bar, nki FROM my_table ORDER BY nki;
SET STATISTICS TIME OFF;

SQL Server Execution Times: CPU time = 32 ms, elapsed time = 106 ms
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

Execution plan


  • 为什么 SQL Server 使用聚集索引加上排序算法而不是使用非聚集索引,即使在后一种情况下执行时间快了 38%?

Rob*_*ley 9

如果您将 100,000 次查找所需的读取次数与进行排序所涉及的次数进行比较,您可能很快就会了解为什么查询优化器认为 CIX+Sort 将是最佳选择。

查找执行最终会更快,因为正在读取的页面在内存中(即使您清除缓存,每页也有很多行,因此您一遍又一遍地读取相同的页面,但碎片数量不同或来自其他活动的不同内存压力,情况可能并非如此)。让 CIX+Sort 运行得更快确实不需要那么多,但是你看到的是因为读取成本没有考虑到重复访问相同页面的相对便宜。


For*_*est 9

为什么 SQL Server 使用聚集索引加上排序算法而不是使用非聚集索引,即使在后一种情况下执行时间快了 38%?

因为 SQL Server 使用基于统计而不是运行时信息的基于成本的优化器。

在此查询的成本估算过程中,它确实会评估查找计划,但估计会花费更多的精力。(在执行计划中将鼠标悬停在 SELECT 上时,请注意“估计子树成本”)。这也不一定是一个糟糕的假设 - 在我的测试机器上,查找计划需要 6 倍于排序/扫描的 CPU。

关于为什么 SQL Server 可能会花费更高的查找计划,请查看 Rob Farley 的回答。