为什么在创建索引时使用INCLUDE子句?


431

在学习70-433考试时,我注意到您可以通过以下两种方式之一创建覆盖指数。

CREATE INDEX idx1 ON MyTable (Col1, Col2, Col3)

- 要么 -

CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)

INCLUDE子句对我来说是新的。在确定是否创建包含或不包含INCLUDE子句的覆盖索引时,您将为什么使用它以及您会建议什么准则?

Answers:


363

如果列不在中WHERE/JOIN/GROUP BY/ORDER BY,而仅在SELECT子句中的列列表中。

INCLUDE子句将数据添加到最低/叶子级别,而不是索引树中。这使索引变小,因为它不是树的一部分

INCLUDE columns不是索引中的关键列,因此它们没有顺序。这意味着如上所述,它对于谓词,排序等并不是真正有用。但是,如果在关键列的几行中有剩余查找,这可能会很有用。

另一篇有关实例的MSDN文章


7
那么,这将是一种用于创建价格便宜的Covered Index版本的技术吗?
JMarsch 2012年

3
@gbn,您介意更详细地解释此句子,并解释为什么它意味着include子句对排序没有用,等等:“ INCLUDE子句将数据添加到最低/叶子级别,而不是索引树中“这使索引变小了,因为它不是树的一部分”
Tola Odejayi

4
@JMarsch:很抱歉收到您的回复,但是是的,这就是事实。
gbn

10
@Tola Odejayi:INCLUDE列不是索引中的关键列,因此它们没有顺序。这使得它们通常对JOIN或排序没有用。而且由于它们不是关键列,因此它们不会像关键列一样位于整个B树结构中
gbn

4
虽然这是最可接受的答案,但我认为需要进一步解释,如果对于某些查询,该列是的一部分,SELECT而对于某些则不是呢?\
Chisko

215

您可以使用INCLUDE将一或多个列添加到非聚集索引的叶级,如果这样做,则可以“覆盖”查询。

假设您需要查询员工的ID,部门ID和姓氏。

SELECT EmployeeID, DepartmentID, LastName
FROM Employee
WHERE DepartmentID = 5

如果您碰巧在(EmployeeID,DepartmentID)上具有非聚集索引,则在找到给定部门的员工后,现在必须进行“书签查找”以获取实际的完整员工记录,而只是获取“姓氏”列。如果您找到很多员工,那么就绩效而言可能会非常昂贵。

如果您在索引中包含该姓氏:

CREATE NONCLUSTERED INDEX NC_EmpDep 
  ON Employee(EmployeeID, DepartmentID)
  INCLUDE (Lastname)

那么您需要的所有信息都可以在非聚集索引的叶级中找到。只需查找非聚集索引并找到给定部门的员工,您便拥有了所有必要的信息,不再需要为索引中找到的每个员工进行书签查找->这样可以节省大量时间。

显然,您不能在每个非聚集索引中包括每一列-但是,如果您确实有一些查询丢失了仅一到两列以“被覆盖”(并且被大量使用),那么将那些包含在内将非常有帮助转换为合适的非聚集索引。


25
您确定要使用此索引吗?为什么是EmployeeID?您仅在关键列中需要DepartmentID吗?您在这里被引用为权威人士:stackoverflow.com/q/6187904/27535
gbn

3
您的解释很好,但实际上与您概述的用例不符。键列应在过滤器或JOIN查询中的键上,并且INCLUDEs必须是您要检索但不能排序的数据。
2012年

15
首先,索引Employee(EmployeeID,DepartmentID)将不用于过滤DepartmentID =5。因为其顺序不匹配
AnandPhadke 2013年

29

这个讨论没有在重要的一点上遗漏了:问题不是“非关键列”最好包含为索引列还是包含列。

问题是,使用包含机制包含索引中实际上不需要的列有多昂贵?(通常不属于where子句,但通常包含在select中)。因此,您的困境始终是:

  1. 在id1,id2 ... idN上单独使用索引或
  2. 在id1,id2 ... idN上使用索引加上include col1,col2 ... colN

其中:id1,id2 ... idN是经常在限制中使用的列,而col1,col2 ... colN是经常选择但在限制中通常使用的列

(将所有这些列作为索引键的一部分的选择总是很愚蠢的(除非它们也用于限制条件)-由于维护索引总是很昂贵的,因为即使必须对索引进行更新和排序,也是如此。 “键”没有更改)。

那么使用选项1或2?

答案:如果您的表很少更新-通常是插入到表中/从表中删除,则使用include机制包含一些“热列”(通常在选择中使用-但在限制上经常使用)相对便宜,因为插入/删除无论如何都需要对索引进行更新/排序,因此,在已经更新索引的同时,存储一些额外的列就不会产生多少额外的开销。开销是用于在索引上存储冗余信息的额外内存和CPU。

如果您考虑添加为包含列的列经常被更新(而没有索引键,则列被更新)- -如果列太多以至于索引接近表的副本,请使用选项1我建议!另外,如果添加某些include-column结果没有任何性能差异-您可能希望跳过添加它们的想法:)验证它们是否有用!

键中每个相同值的平均行数(id1,id2 ... idN)也可能很重要。

请注意,如果在限制中使用一列(作为包含的索引-column添加):只要可以使用这样的索引(基于对索引 -columns的限制),则SQL Server匹配对索引(叶节点值)的列限制,而不是绕表本身进行昂贵的操作。


18

基本索引列已排序,但包含的列未排序。这样可以节省维护索引的资源,同时仍然可以在包含的列中提供数据来覆盖查询。因此,如果要涵盖查询,则可以放置搜索条件以将行定位到索引的已排序列中,然后使用非搜索数据“包括”其他未排序的列。它绝对有助于减少索引维护中的排序和碎片数量。


7

很好解释了原因(包括索引叶级中的数据)的原因。您对此进行两点动摇的原因是,当您运行查询时,如果没有包含附加列(SQL 2005中的新功能),则SQL Server必须转到聚簇索引以获取附加列当新的数据页被加载到内存中时,这将花费更多的时间,并增加SQL Server服务,磁盘和内存(特定于缓冲区缓存)的负载,从而有可能将其他更常用的数据从缓冲区缓存中推出。


有没有办法证明它实际上使用的内存更少?这也是我所期望的,但是我在工作中对此有所了解
Asken 2012年

鉴于您必须将页面从堆索引或聚集索引加载到内存以及索引页面,这意味着您要将重复数据放入内存,因此数学变得非常简单。至于专门衡量它的方法,没有。
mrdenny

5

我在已经给出的答案中没有看到的另一个考虑因素是,包含的列可以是不允许作为索引键列的数据类型,例如varchar(max)。

这使您可以将这些列包括在覆盖索引中。最近,我不得不执行此操作以提供nHibernate生成的查询,该查询在SELECT中有很多列,并带有有用的索引。


3

如果您不需要INCLUDE键中的该列,首选键列的一个原因是文档。这样一来,不断发展的索引将来将变得更加容易。

考虑您的示例:

CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)

如果您的查询如下所示,则该索引为最佳:

SELECT col2, col3
  FROM MyTable
 WHERE col1 = ...

当然,INCLUDE如果您可以从关键部分中获得更多好处,也不要放入列。以下两个查询实际上都希望col2使用索引键中的列。

SELECT col2, col3
  FROM MyTable
 WHERE col1 = ...
   AND col2 = ...
SELECT TOP 1 col2, col3
  FROM MyTable
 WHERE col1 = ...
 ORDER BY col2

假设情况并非如此,我们col2INCLUDE子句中使用它,因为将其包含在索引的树部分中没有任何好处。

快进几年。

您需要调整此查询:

SELECT TOP 1 col2
  FROM MyTable
 WHERE col1 = ...
 ORDER BY another_col

要优化该查询,以下索引将非常有用:

CREATE INDEX idx1 ON MyTable (Col1, another_col) INCLUDE (Col2)

如果您已经检查了该表上的哪些索引,则先前的索引可能仍然存在:

CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)

现在,您知道Col2并且Col3它们不是索引树的一部分,因此不用于缩小读取索引范围或对行进行排序。将is添加another_column到索引的关键部分的末尾(在之后col1)相当安全。破坏任何东西的风险很小:

DROP INDEX idx1 ON MyTable;
CREATE INDEX idx1 ON MyTable (Col1, another_col) INCLUDE (Col2, Col3);

该索引将变大,仍然存在一些风险,但是与引入新索引相比,扩展现有索引通常更好。

如果您有一个不带索引的索引INCLUDE,那么您将无法知道通过another_col在其后添加会中断哪些查询Col1

CREATE INDEX idx1 ON MyTable (Col1, Col2, Col3)

如果another_colCol1和之间加,会发生什么Col2?其他查询会受到影响吗?

如果您添加INCLUDE vs.关键列还有其他“好处”,只是为了避免从表中获取它们。但是,我认为文档方面是最重要的方面。

要回答您的问题:

在确定是否创建包含或不包含INCLUDE子句的覆盖索引时,您将建议什么准则?

如果仅出于在索引中可用该列而不访问该表的目的,则将其添加到索引中,请将其放入INCLUDE子句中。

如果将列添加到索引键带来了其他好处(例如,order by因为或因为它可以缩小读取索引范围),请将其添加到键中。

您可以在此处阅读有关此内容的更长的讨论:

https://use-the-index-luke.com/blog/2019-04/include-columns-in-btree-indexes


2

内联到索引定义中的所有列的总大小是有限的。话虽这么说,我从来不需要创建那么宽的索引。对我而言,更大的优势是您可以使用一个包含列的索引来覆盖更多查询,因为它们不必以任何特定顺序进行定义。认为是索引内的一个索引。一个示例是StoreID(其中StoreID是低选择性的,这意味着每个商店都与很多客户相关联),然后是客户统计数据(姓,名,名):如果您仅按以下顺序内嵌这些列(StoreID,LastName) ,FirstName,DOB),则只能有效地搜索您知道StoreID和LastName的客户。

另一方面,在StoreID上定义索引并包括LastName,FirstName,DOB列实际上将使您在StoreID上执行两个seeks-index谓词,然后在任何包含的列上寻找谓词。只要它以StoreID开头,就可以覆盖所有可能的搜索排列。

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.