为什么我的WHERE子句从“包含”列中受益?


12

根据此答案,除非在用于限制的列上建立索引,否则查询将不会从索引中受益。

我有这个定义:

CREATE TABLE [dbo].[JobItems] (
    [ItemId]             UNIQUEIDENTIFIER NOT NULL,
    [ItemState]          INT              NOT NULL,
    [ItemPriority]       INT NOT NULL,
    [CreationTime]       DATETIME         NULL DEFAULT GETUTCDATE(),
    [LastAccessTime]     DATETIME         NULL DEFAULT GETUTCDATE(),
     -- other columns
 );

 CREATE UNIQUE CLUSTERED INDEX [JobItemsIndex]
    ON [dbo].[JobItems]([ItemId] ASC);
 GO

CREATE INDEX [GetItemToProcessIndex]
    ON [dbo].[JobItems]([ItemState], [ItemPriority], [CreationTime])
    INCLUDE (LastAccessTime);
GO

和这个查询:

UPDATE TOP (150) JobItems 
SET ItemState = 17 
WHERE 
    ItemState IN (3, 9, 10)
    AND LastAccessTime < DATEADD (day, -2, GETUTCDATE()) 
    AND CreationTime < DATEADD (day, -2, GETUTCDATE());

我查看了实际的计划,并且只有一个谓词与“完全”中的谓词完全一样WHERE-没有额外的“书签查找”来检索,LastAccessTime即使后者只是“包含”在索引中,而不是索引的一部分。

在我看来,这种行为与列必须是索引的一部分而不仅仅是“包含”的规则相矛盾。

我观察到的行为是正确的吗?我如何提前知道我是WHERE从包含列中受益还是需要该列成为索引的一部分?


它仍然可以基于该ItemState值进行搜索,但是“搜索”的效率不如您的索引结构如下(ItemState, CreationTime, LastAccessTime)
Mark Sinkinson

1
@MarkSinkinson或只是(ItemState, CreationTime) INCLUDE (LastAccessTime)
ypercubeᵀᴹ

@sharptooth您所拥有的链接答案并没有说(“除非在用于限制查询的列上建立索引,否则索引将无法受益”)。它说索引on (a,b)并不是最适合使用的查询,SELECT a FROM t WHERE b=5;索引on (b) INCLUDE (a)更好。
ypercubeᵀᴹ

Answers:


9

您的谓词不同于您的寻求谓词。

搜索谓词用于搜索索引中的有序数据。在这种情况下,它将进行三个查找,每个感兴趣的ItemState进行一次查找。除此之外,数据按ItemPriority顺序排列,因此无法进行进一步的“搜索”操作。

但是在返回数据之前,它会使用谓词(我称为“剩余谓词”)检查每一行。它是根据Seek谓词的结果完成的。

任何包含的列都不是有序数据的一部分,但是可以用来满足残差谓词,而不必执行额外的查找。

您可以看到我关于可精性的内容。特别是在SQLBits上检查会话,网址为http://bit.ly/Sargability

编辑:为了更好地显示残差的影响,请使用未记录的来运行查询,该查询OPTION (QUERYTRACEON 9130)会将残差分离为单独的Filter运算符(实际上是计划的早期版本,然后残差移至Seek运算符)。通过向左传递到筛选器的行数,它清楚地显示了无效搜索的影响。

还要注意的是,由于ItemState上的IN子句,因此左传递的数据实际上是按ItemState顺序而不是按ItemPriority顺序。ItemState上的复合索引后跟一个日期(例如(ItemState,LastAccessTime))可以用于具有三个Seek(请注意,Seek谓词在一个Seek运算符中显示三个搜索),每个针对两个级别,产生的数据为仍处于ItemState顺序(例如,ItemState = 3并且LastAccessTime小于某个值,然后ItemState = 9并且LastAccessTime小于某个值,然后ItemState = 10并且LastAccessTime小于某个值)。

(ItemState,LastAccesTime,CreationTime)上的索引不会比(ItemState,LastAccessTime)上的索引有用,因为CreationTime级别仅在您的搜索针对特定ItemState和LastAccessTime组合(而不是范围)时才有用。就像如果您对以F开头的姓氏感兴趣的话,电话簿不是按照名字的顺序排列的。

如果您想要一个复合索引,但是由于您使用较早的列的方式而永远无法使用“查找谓词”中的较晚的列,那么您最好将它们与包含的列一样使用,因为它们在包含的列中占用的空间更少索引(因为它们仅存储在索引的叶级别,而不是更高级别),但仍可以避免查找并在残差谓词中使用。

按照术语“剩余谓词”-这是我对Seek属性的专有名词。合并联接显式地将其等同称为残差谓词,而哈希匹配将其等同称为探测残差(如果您匹配哈希,则可以从TSA获得)。但是在Seek中,他们仅将其称为Predicate,这使它看起来不那么糟糕。


3

GetItemToProcessIndex不是完全可搜索的,因为您的where子句处于启用状态ItemState + LastAccessTime + CreationTime。索引列和where子句不是完美匹配。

如果在上创建覆盖索引ItemState + LastAccessTime + CreationTime,则对于从GetItemToProcessIndex获得的每个匹配项,还将获得主键(ItemId)的值。只需确保第二个日期匹配即可。

这就是您所需要的,然后跳到该行在其页面上的位置并进行更新。

使用当前索引,它可以帮助服务器查找具有所需ItemState的行,但是它仍必须从索引中读取所有这些行,以便在LastAccessTime + CreationTime上找到正确的匹配项。根据日期谓词和匹配集的大小以及必须排除的内容,它可能会导致比仅查找ItemState和第二列(第一个索引日期)的3列完全覆盖索引更好的IO。 。不过,可以包含索引中的第二个日期。尽管可以在第3列之间建立索引,但不应在这3列之间建立额外的索引(请参阅关于额外列的rob的答案)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.