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

Cor*_*ory 416 sql-server indexing sql-server-2005 sql-server-2008

在学习70-433考试的同时,我注意到你可以用以下两种方式之一创建覆盖索引.

CREATE INDEX idx1 ON MyTable (Col1, Col2, Col3)
Run Code Online (Sandbox Code Playgroud)

- 要么 -

CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)
Run Code Online (Sandbox Code Playgroud)

INCLUDE条款对我来说是新的.为什么要使用它以及在确定是否使用INCLUDE子句创建覆盖索引时,您会建议使用什么准则?

gbn*_*gbn 352

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

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

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

另一篇MSDN文章,附有一个实例

  • @Tola Odejayi:INCLUDE列不是索引中的键列,因此它们不是有序的.这使得它们*通常*对JOIN或排序无用.并且因为它们不是关键列,所以它们不像关键列一样位于整个B树结构中 (10认同)
  • 那么,这将是一种创建覆盖索引的较便宜版本的技术吗? (7认同)
  • @JMarsch:对于迟到的回复感到抱歉,但是,这就是它的确如此. (4认同)
  • 虽然这是最常被接受的答案,但我认为需要进一步解释,如果对于某些查询,列是"SELECT"的一部分,而有些则不是?\ (4认同)
  • @gbn,您是否会更详细地解释这句话,并解释为什么它意味着include子句对排序等没有用处:"INCLUDE子句在最低/叶级别添加数据,而不是在索引树中这使索引更小,因为它不是树的一部分" (3认同)

mar*_*c_s 210

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

想象一下,您需要查询员工的ID,部门ID和姓氏.

SELECT EmployeeID, DepartmentID, LastName
FROM Employee
WHERE DepartmentID = 5
Run Code Online (Sandbox Code Playgroud)

如果您碰巧在(EmployeeID,DepartmentID)上有非聚集索引,一旦找到给定部门的员工,您现在必须执行"书签查找"以获取实际的完整员工记录,只需获取lastname列.如果找到很多员工,那么在性能方面可能会非常昂贵.

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

CREATE NONCLUSTERED INDEX NC_EmpDep 
  ON Employee(EmployeeID, DepartmentID)
  INCLUDE (Lastname)
Run Code Online (Sandbox Code Playgroud)

然后,您需要的所有信息都可以在非聚集索引的叶级别中获得.只需在非聚集索引中查找并找到给定部门的员工,就可以获得所有必要的信息,并且不再需要在索引中找到的每个员工的书签查找 - >节省了大量时间.

显然,你不能在每个非聚集索引中包含每一列 - 但是如果你确实有一些查询只缺少一两列要被"覆盖"(并且经常使用),那么对它们进行包含会很有帮助.到一个合适的非聚集索引.

  • 你确定你使用这个索引吗?为何选择EmployeeID?您只需要在关键列中使用DepartmentID?你在这里被引用为authoratitive:http://stackoverflow.com/q/6187904/27535 (23认同)
  • 首先,不会使用索引Employee(EmployeeID,DepartmentID)来过滤DepartmentID = 5.因为它的顺序不匹配 (14认同)
  • 您的解释很好,但实际上并不符合您概述的用例.键列应位于查询中的过滤器或"JOIN"键上,并且"INCLUDE"需要是您要检索但未排序的数据. (3认同)

小智 27

这个讨论遗漏了重要的一点:问题不在于"非关键列"是否更好地包含为index -columns或包含 -columns.

问题是使用包含机制来包含索引中不需要的列有多昂贵?(通常不是where子句的一部分,但通常包括在选择中).所以你的困境总是:

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

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

(将所有这些列作为索引键的一部分包含在内的选项总是很愚蠢(除非它们也用于限制) - 因为它的维护总是更昂贵,因为索引必须更新和排序,即使"钥匙"没有改变).

那么使用选项1还是2?

答:如果您的表很少更新 - 大多数插入/删除 - 那么使用包含机制来包含一些"热列"(通常用于选择 - 但通常用于限制)是相对便宜的插入/删除要求索引无论如何都要更新/排序,因此在已经更新索引的同时存储少量额外列的额外开销很少.开销是用于在索引上存储冗余信息的额外内存和CPU.

如果您认为要添加为包含列的列经常更新(没有更新index- key -columns) - 或者 - 如果它们中的许多列使索引变得接近表的副本 - 请使用选项1我建议!此外,如果添加某些包含列的结果不会产生性能差异 - 您可能想要跳过添加它们的想法:)验证它们是否有用!

键中每个相同值的平均行数(id1,id2 ... idN)也可能具有一定的重要性.

请注意,如果在限制中使用了一列(作为索引的包含列添加):只要可以使用索引(基于对index- key -columns的限制) - 那么SQL Server就匹配了对索引的列限制(叶节点值)而不是围绕表本身的昂贵方式.


onu*_*ade 17

基本索引列已排序,但包含的列未排序.这节省了维护索引的资源,同时仍然可以提供包含列中的数据以覆盖查询.因此,如果要覆盖查询,可以使用搜索条件将行定位到索引的已排序列中,然后使用非搜索数据"包含"其他未排序的列.它肯定有助于减少索引维护中的排序和碎片数量.


mrd*_*nny 6

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


Rob*_*mes 5

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

这允许您在覆盖索引中包含此类列.我最近不得不这样做来提供一个nHibernate生成的查询,它在SELECT中有很多列,带有一个有用的索引.


Mar*_*and 5

如果您不需要INCLUDE键中的该列,那么更喜欢键列的一个原因是文档。这使得未来不断发展的索引变得更加容易。

考虑你的例子:

CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)
Run Code Online (Sandbox Code Playgroud)

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

SELECT col2, col3
  FROM MyTable
 WHERE col1 = ...
Run Code Online (Sandbox Code Playgroud)

当然,INCLUDE如果您可以通过将它们放在关键部分中获得额外的好处,则不应将它们放入。以下两个查询实际上更喜欢col2索引键中的列。

SELECT col2, col3
  FROM MyTable
 WHERE col1 = ...
   AND col2 = ...
Run Code Online (Sandbox Code Playgroud)
SELECT TOP 1 col2, col3
  FROM MyTable
 WHERE col1 = ...
 ORDER BY col2
Run Code Online (Sandbox Code Playgroud)

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

快进几年。

您需要调整此查询:

SELECT TOP 1 col2
  FROM MyTable
 WHERE col1 = ...
 ORDER BY another_col
Run Code Online (Sandbox Code Playgroud)

要优化该查询,以下索引会很棒:

CREATE INDEX idx1 ON MyTable (Col1, another_col) INCLUDE (Col2)
Run Code Online (Sandbox Code Playgroud)

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

CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)
Run Code Online (Sandbox Code Playgroud)

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

DROP INDEX idx1 ON MyTable;
CREATE INDEX idx1 ON MyTable (Col1, another_col) INCLUDE (Col2, Col3);
Run Code Online (Sandbox Code Playgroud)

该指数会变得更大,这仍然存在一些风险,但与引入新指数相比,扩展现有指数通常更好。

如果您有一个没有 的索引INCLUDE,您将无法知道another_colCol1.

CREATE INDEX idx1 ON MyTable (Col1, Col2, Col3)
Run Code Online (Sandbox Code Playgroud)

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

如果您添加这些列只是为了避免从 table 中获取它们,那么INCLUDEvs. key 列还有其他“好处” 。但是,我认为文档方面是最重要的方面。

回答你的问题:

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

如果将一列添加到索引的唯一目的是在索引中使用该列而不访问表,请将其放入INCLUDE子句中。

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

你可以在这里阅读更长的讨论:

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