在准备70-433考试时,我注意到可以用以下两种方法之一创建覆盖索引。

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

——或——

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

INCLUDE条款对我来说很陌生。你为什么要使用它,在决定是否使用INCLUDE子句创建覆盖索引时,你有什么指导方针?


当前回答

这个讨论忽略了重要的一点:问题不在于“非键列”作为索引列更好,还是作为包含列更好。

问题是使用包含机制来包含索引中并不真正需要的列的代价有多大?(通常不是where-子句的一部分,但通常包含在select中)。所以你的困境总是:

在id1上使用索引,id2…单独idN或 在id1上使用索引,id2…idN + include col1, col2…colN

地点: Id1, id2…idN是常用于限制的列,col1, col2…colN是经常选择的列,但通常不用于限制

(将所有这些列作为index-key的一部分的选项总是愚蠢的(除非它们也用于限制)——因为它总是维护更昂贵,因为即使“键”没有改变,索引也必须更新和排序)。

所以选择1还是2?

Answer: If your table is rarely updated - mostly inserted into/deleted from - then it is relatively inexpensive to use the include-mechanism to include some "hot columns" (that are often used in selects - but not often used on restrictions) since inserts/deletes require the index to be updated/sorted anyway and thus little extra overhead is associated with storing off a few extra columns while already updating the index. The overhead is the extra memory and CPU used to store redundant info on the index.

如果您考虑添加作为包含的列—列经常更新(索引键列没有更新)—或者—如果它们太多以至于索引变得接近您的表的副本—我建议使用选项1 !此外,如果添加某些include-column(s)结果没有产生性能差异-你可能想要跳过添加它们的想法:)验证它们是有用的!

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

请注意,如果一个列—作为索引的包含列添加—在限制中使用:只要这样的索引可以使用(基于对索引-键-列的限制)—那么SQL Server就会根据索引(叶-节点-值)匹配列限制,而不是在表本身周围使用昂贵的方法。

其他回答

这个讨论忽略了重要的一点:问题不在于“非键列”作为索引列更好,还是作为包含列更好。

问题是使用包含机制来包含索引中并不真正需要的列的代价有多大?(通常不是where-子句的一部分,但通常包含在select中)。所以你的困境总是:

在id1上使用索引,id2…单独idN或 在id1上使用索引,id2…idN + include col1, col2…colN

地点: Id1, id2…idN是常用于限制的列,col1, col2…colN是经常选择的列,但通常不用于限制

(将所有这些列作为index-key的一部分的选项总是愚蠢的(除非它们也用于限制)——因为它总是维护更昂贵,因为即使“键”没有改变,索引也必须更新和排序)。

所以选择1还是2?

Answer: If your table is rarely updated - mostly inserted into/deleted from - then it is relatively inexpensive to use the include-mechanism to include some "hot columns" (that are often used in selects - but not often used on restrictions) since inserts/deletes require the index to be updated/sorted anyway and thus little extra overhead is associated with storing off a few extra columns while already updating the index. The overhead is the extra memory and CPU used to store redundant info on the index.

如果您考虑添加作为包含的列—列经常更新(索引键列没有更新)—或者—如果它们太多以至于索引变得接近您的表的副本—我建议使用选项1 !此外,如果添加某些include-column(s)结果没有产生性能差异-你可能想要跳过添加它们的想法:)验证它们是有用的!

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

请注意,如果一个列—作为索引的包含列添加—在限制中使用:只要这样的索引可以使用(基于对索引-键-列的限制)—那么SQL Server就会根据索引(叶-节点-值)匹配列限制,而不是在表本身周围使用昂贵的方法。

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

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

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

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

INCLUDE列不是索引中的键列,因此它们没有排序。 这意味着它对于我上面提到的谓词、排序等并不是很有用。但是,如果从键列开始的几行中有残留查找,那么它可能会很有用。

另一篇MSDN文章提供了一个实际示例

如果您不需要键中的列,那么选择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

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

几年过去了。

你需要调优这个查询:

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在索引的键部分的末尾(在col1之后)添加another_column是相当安全的。打破任何东西的风险很小:

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

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

如果你有一个没有INCLUDE的索引,你不知道在Col1之后添加another_col会中断什么查询。

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

如果在Col1和Col2之间添加another_col会发生什么?其他查询是否会受到影响?

如果添加这些列只是为了避免从表中获取它们,那么INCLUDE与键列相比还有其他“好处”。然而,我认为文档方面是最重要的。

回答你的问题:

在决定是否使用INCLUDE子句创建覆盖索引时,您有什么建议?

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

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

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

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

基本索引列被排序,但包含的列不被排序。这节省了维护索引的资源,同时仍然可以在所包含的列中提供数据来覆盖查询。因此,如果您想涵盖查询,您可以将搜索条件放在索引的已排序列中定位行,但随后“包括”其他具有非搜索数据的未排序列。它确实有助于减少索引维护中的排序和碎片量。