为什么要进行聚集索引扫描?

Pio*_*rak 4 sql-server execution-plan sql-server-2012 sql-server-express

我刚刚开始学习优化我的查询并分析他们的查询计划。我认为这个查询会生成非聚集索引查找 + 键查找。

SELECT ct.*
FROM Person.ContactType AS ct
WHERE ct.Name LIKE 'Own%';
Run Code Online (Sandbox Code Playgroud)

相反,它使用聚集索引扫描。我不知道为什么。

我正在 SQL Server 2012 Express 上使用 AdventureWorks2012 数据库。ContactTypeId 列上有聚集索引,Name 列上有非聚集索引。第三列 (ModifiedDate) 不属于任何索引。该表仅包含 20 行。

我怀疑查询优化器决定进行聚集索引扫描,因为表只有 20 行,也许扫描索引然后进行键查找更快。

Mar*_*ith 12

这张桌子很小!

它有 20 行,其中 2 行符合搜索条件。表定义包含三列和两个索引(都支持唯一性约束)。

CREATE TABLE Person.ContactType(
    ContactTypeID int IDENTITY(1,1) NOT NULL,
    Name dbo.Name NOT NULL,
    ModifiedDate datetime NOT NULL,
    CONSTRAINT PK_ContactType_ContactTypeID PRIMARY KEY CLUSTERED(ContactTypeID),
    CONSTRAINT AK_ContactType_Name UNIQUE NONCLUSTERED(Name)
) 
Run Code Online (Sandbox Code Playgroud)

跑步

SELECT index_type_desc,
       index_depth,
       page_count,
       avg_page_space_used_in_percent,
       avg_record_size_in_bytes
FROM   sys.dm_db_index_physical_stats(db_id(), 
                                      object_id('Person.ContactType'), 
                                      NULL, 
                                      NULL, 
                                      'DETAILED') 
Run Code Online (Sandbox Code Playgroud)

显示两个索引仅由单个叶页组成,没有上级页。

+--------------------+-------------+------------+--------------------------------+--------------------------+
|  index_type_desc   | index_depth | page_count | avg_page_space_used_in_percent | avg_record_size_in_bytes |
+--------------------+-------------+------------+--------------------------------+--------------------------+
| CLUSTERED INDEX    |           1 |          1 | 15.9130219915987               | 62.5                     |
| NONCLUSTERED INDEX |           1 |          1 | 13.1949592290586               | 51.5                     |
+--------------------+-------------+------------+--------------------------------+--------------------------+
Run Code Online (Sandbox Code Playgroud)

每个索引页面上的行不一定按索引键顺序排列,但每个页面都有一个槽数组,其中包含页面上每一行的偏移量。这是按索引顺序维护的。

非聚集索引覆盖了三列中的两列(Name 作为键列,ContactTypeID 作为返回到基表的行定位器),但缺少ModifiedDate

您可以使用索引提示来强制 NCI 搜索如下

SELECT ct.*
FROM   Person.ContactType AS ct WITH (INDEX = AK_ContactType_Name)
WHERE  ct.Name LIKE 'Own%'; 
Run Code Online (Sandbox Code Playgroud)

但是您可以看到,在 SQL Server 的成本模型下,该计划的估计成本高于竞争性 CI 扫描(大约两倍)。

在此处输入图片说明

单页聚集索引扫描只需要读取页面上的所有 20 行,根据它们评估谓词并返回它们。

单页非聚集索引范围查找可能能够对槽数组执行二进制搜索以减少评估的行数,但是索引不覆盖查询,因此它还需要潜在的 IO 来检索 CI 页,然后它仍然需要找到缺少列值的行(对于 NCI 搜索返回的每一行)。

在我的机器上运行 100 万次非聚集索引计划迭代需要15.245几秒钟,而聚集索引计划需要几11.113秒钟。虽然这远不是​​没有提示的计划的两倍,但明显更快。

即使表格大了几个数量级,但是您可能仍然无法通过查找获得预期的计划。

SQL Server 的成本计算模型更喜欢顺序扫描而不是随机 IO 查找,并且它选择覆盖索引的扫描或非覆盖索引的查找和查找之间的“临界点”通常低得惊人,如Kimberley Tripp 的博客文章中所述

为 10% 选择性谓词选择这样的计划当然不是不可能的,但聚集索引可能需要比 NCI 宽得多才能这样做。

  • 我(还)不了解所有内容,但这是我在所有 stachexchange 网站上收到的最好的答案! (3认同)