缺少非聚集索引已经是聚集索引的一部分

Fle*_*tch 9 index sql-server execution-plan sql-server-2016

我正在调试运行缓慢的查询,并且在执行计划中建议使用非聚集索引,影响为 51.6648。但是,非聚集索引仅包括主键 (PK) 复合聚集索引中已经存在的列。

这可能是因为索引中列的顺序吗?即,如果聚集索引中的列不是按选择性从高到低的顺序排列,那么非聚集索引是否有可能提高性能?

此外,非聚集索引仅包含三个 PK 列中的两个,第三个添加为包含列。include使用非聚集索引可能更优化的另一个原因是什么?

下面是我正在使用的表结构的示例:

表-

Retailers (
    RetailerID int PK, 
    name ...)

Retailer_Relation_Types (
    RelationType smallint PK, 
    Description nvarchar(50) ...)

Retailer_Relations (
    RetailerID int PK FK, 
    RelatedRetailerID int PK FK, 
    RelationType smallint PK FK, 
    CreatedOn datetime ...)
Run Code Online (Sandbox Code Playgroud)

该表Retailer_Relations具有以下复合PK指数和建议指数-

CONSTRAINT PK_Retailer_Relations 
PRIMARY KEY CLUSTERED (
    RetailerID ASC, 
    RelatedRetailerID ASC, 
    RelationType ASC
    ) ON [PRIMARY]

CREATE NONCLUSTERED INDEX <NameOfIndex> 
ON Retailer_Relations (
    RetailerID, 
    RelationType
    ) 
INCLUDE (
    RelatedRetailerID
    )
Run Code Online (Sandbox Code Playgroud)

Ran*_*gen 12

表 Retailer_Relations 具有以下复合 PK 索引和建议索引 -

虽然缺少索引可能会有所帮助并且肯定可以工作,但我不会在缺少索引上花费太多时间,这些提示是在估计的执行计划上创建的,而不是在实际执行计划上。

更准确地说,这些索引提示是以降低计划中运营商使用的Query Bucks™成本为前提的。优化器计算估计成本,并相应地添加缺失的索引提示。

结果他们可能是非常错误的。如果您不确定它是否会有所帮助,最好的做法是在前后测试情况。您可以通过SET STATISTICS IO, TIME ON;在运行查询之前添加语句来完成此 操作。

此外,您可以使用statisticsparser来更轻松地阅读这些统计信息。

这可能是因为索引中列的顺序吗?

没错,创建缺失索引可以提高查询的选择性,例如,如果您的查询如下所示:

SELECT  RelatedRetailerID
FROM Retailer_Relations 
WHERE
RetailerID = 5 AND
RelationType = 20;
Run Code Online (Sandbox Code Playgroud)

或者像这样:

SELECT  RelatedRetailerID
FROM Retailer_Relations 
ORDER BY
RetailerID,
RelationType;
Run Code Online (Sandbox Code Playgroud)

这背后的原因是两个索引都可以在 RetailerID 上查找,这部分不会改变。但是,如果在 RelationType 上应用了额外的过滤器/排序呢?它会在聚集索引中无处不在,因为它是第三个键值,而不是第二个键值。正如我们所知,它是 NCI 中的第二个关键值。

好的,但是非聚集索引何时或如何改进查询?

几种情况可能是:

  • 如果relationType 过滤了很多值,剩余的I/O 可能会很高,导致可能需要非聚集索引(查询#1)
  • 对两列进行排序(一种方式),并且结果集很大(查询 #2)。
  • 正如@AaronBertrand 提到的:如果 CI 大小与 NCI 相比差异很大,添加 NCI 将减少从中受益的查询读取的页面。

NCI 旁注

作为旁注,并不完全需要将键列添加到 NCI 中的包含列表中,因为 CI 键列会自动包含在所有非聚集索引中。

如果您不确定聚集索引是否会保持不变,并且希望始终包含该列,则可以选择这样做。

关于查询本身,如果您通过PasteThePlan添加执行计划, 我们可以提供有关索引/改进查询的更多信息。


测试

创建表并添加一些行

CREATE TABLE Retailer_Relations (
    RetailerID int , 
    RelatedRetailerID int , 
    RelationType smallint, 
    CreatedOn datetime,
    CONSTRAINT PK_Retailer_Relations 
PRIMARY KEY CLUSTERED (
    RetailerID ASC, 
    RelatedRetailerID ASC, 
    RelationType ASC
    ) ON [PRIMARY])


    DECLARE @I Int = 1
    WHILE @I < 1000
    BEGIN
    INSERT INTO Retailer_Relations(RetailerID,RelatedRetailerID,RelationType,CreatedOn)
    VALUES(@I,@I,@I,GETDATE()
    )
    set @I += 1
    END
Run Code Online (Sandbox Code Playgroud)

查询#1

    SELECT  RelatedRetailerID
FROM Retailer_Relations 
WHERE
RetailerID = 5 AND
RelationType = 20;
Run Code Online (Sandbox Code Playgroud)

没有索引的计划在这里

当它进行搜索时,它正在对 RetailerID 进行搜索。之后它在 RelationType 上发出一个残留的 I/O 谓词

添加索引

CREATE NONCLUSTERED INDEX IX_TEST
ON Retailer_Relations (
    RetailerID, 
    RelationType
    ) 
INCLUDE (
    RelatedRetailerID
    )
Run Code Online (Sandbox Code Playgroud)

剩余谓词消失了,一切都发生在两列上的搜索谓词中。

执行计划

通过第二个查询,增加的索引有用性变得更加明显:

SELECT  RelatedRetailerID
FROM Retailer_Relations 
ORDER BY
RetailerID,
RelationType;
Run Code Online (Sandbox Code Playgroud)

没有索引的计划,使用 Sort 运算符:

在此处输入图片说明

用索引规划,用索引去掉排序操作符

在此处输入图片说明