SQL Server 选择非选择性索引

Art*_*yan 4 index sql-server optimization database-internals

我正在测试 SQL Server 索引并发现非常奇怪的行为。这是我的代码:

DROP TABLE IF EXISTS  dbo._Test
DROP TABLE IF EXISTS  dbo._Newtest
GO
CREATE TABLE _Test(
ID INT NOT NULL, 
UserSystemID INT NOT NULL, 
Age INT
)
GO
INSERT INTO dbo._Test
        ( ID, UserSystemID, Age )

SELECT TOP 10000000 ABS(CHECKSUM(NEWID())) % 5000000, ABS(CHECKSUM(NEWID())) % 2, ABS(CHECKSUM(NEWID())) % 100
FROM sys.all_columns
CROSS JOIN sys.all_objects a
CROSS JOIN sys.all_objects b
CROSS JOIN sys.all_objects c

; WITH cte AS (
SELECT ID, UserSystemID,  age, ROW_NUMBER() OVER(PARTITION BY ID, UserSystemID ORDER BY GETDATE()) rn
FROM dbo._Test
)

SELECT cte.ID ,
       cte.UserSystemID ,
       cte.Age
INTO _newTest
FROM cte
WHERE cte.rn = 1

CREATE UNIQUE NONCLUSTERED INDEX  IX_test ON dbo._NewTest(ID, UserSystemID) INCLUDE(age)
GO
ALTER TABLE dbo._NewTest ADD CONSTRAINT PK_NewTest PRIMARY KEY CLUSTERED(UserSystemID, ID)
GO
Run Code Online (Sandbox Code Playgroud)

此时,我在同一个表和相同的列上有两个索引。第一个是非聚类的,第二个是聚类的。该Id列更具选择性(大约 5000000 个唯一值)并且UserSystemID不是(两个唯一值)。

然后我运行以下查询来测试使用了哪个索引:

SELECT id,  UserSystemID, age   
FROM _NewTest
WHERE id = 1502945
AND UserSystemID = 1
Run Code Online (Sandbox Code Playgroud)

它寻找聚集索引。您可以在此处查看该计划。

问题是为什么 SQL Server 更喜欢聚集索引而不是唯一的非聚集索引。


我的聚集索引的前导列的选择性远低于其他唯一的非聚集索引。所以我预计聚集索引的性能一定会更差,但实际上并非如此。

Pau*_*ite 11

给定唯一索引,您的查询将最多选择一行。

优化器知道它只需要下降索引 b 树一次,并且不需要从该点向前或向后扫描以找到更多匹配项。这称为单例搜索(对唯一索引的相等性测试)。

当前的索引匹配实现在可以使用单例搜索时总是选择聚集索引。

这里聚集索引和非聚集索引的选择一般不是很重要。由于导航 b 树的上层(使用二分搜索或线性插值),可能会产生很小的额外成本,但这甚至很难衡量。请记住,非叶索引页面上仅存在IDUserSystemID关键组件。

有人可能会争辩说,平均而言,更广泛的聚集索引叶页在内存中的可能性更小。还有一些其他极端情况的后果,但我认为这种行为不会很快改变。

但是我的聚集索引的前列比其他唯一的非聚集索引的选择性要低得多。所以我预计聚集索引的性能一定会更差,但实际上并非如此。

选择性对于复合 b 树索引上的等式查找无关紧要。

您唯一的聚集复合索引具有键 (UserSystemID, id)。

要查找具有 (UserSystemID = 1 and id = 1502945) 的行,SQL Server 不会查找 UserSystemID = 1 的所有行,然后查找 id = 1502945 的行。这将非常低效。

您可以使用SET STATISTICS IO ON. 您的示例构建了一个具有两个非叶级别的聚集索引。总的来说,找到你想要的行意味着接触三页——索引的每一层都有一页。

行在索引中按 UserSystemID 和 id 排序。我的演示表副本在聚集索引的根(顶级)页面上具有以下布局:

根页面

在此页面上执行二分搜索很容易:

  • 从中间行开始。
  • 将 UserSystemID 与您要查找的进行比较。
    • 如果不相等,则以通常的方式继续二分查找(根据需要在较早或较晚的行中选择一个新的中点)。
    • 如果 UserSystemID 相等,则将 ID 与您要查找的 ID 进行比较,然后继续二分查找

遵循该逻辑,我们将快速找到搜索键所在的子(下一级)索引页面(如果它们存在)。在该页上重复二分查找,依此类推,直到我们到达必须包含我们正在查找的行(如果存在)的单个叶级页。