在准备 70-433 考试时,我注意到您可以通过以下两种方式之一创建覆盖索引。
CREATE INDEX idx1 ON MyTable (Col1, Col2, Col3)
- 或者 -
CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)
INCLUDE 子句对我来说是新的。为什么要使用它?在确定是否创建包含或不包含 INCLUDE 子句的覆盖索引时,您会建议哪些指导方针?
如果列不在 WHERE/JOIN/GROUP BY/ORDER BY
中,而仅在列列表中,则 SELECT
子句是您使用 INCLUDE
的地方。
INCLUDE
子句在最低/叶级别添加数据,而不是在索引树中。这使得索引更小,因为它不是树的一部分
INCLUDE columns
不是索引中的键列,因此它们没有排序。这意味着它对于我上面提到的谓词、排序等并不是很有用。但是,如果您在键列的几行中有残差查找,它可能很有用
Another MSDN article with a worked example
您将使用 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)
那么您需要的所有信息都在非聚集索引的叶级别中可用。只需在非聚集索引中查找并找到给定部门的员工,您就拥有了所有必要的信息,并且不再需要在索引中找到的每个员工的书签查找 --> 您可以节省大量时间。
显然,您不能在每个非聚集索引中包含每一列 - 但如果您确实有查询缺少一两列要“覆盖”(并且经常使用),那么包含这些可能会非常有帮助成合适的非聚集索引。
JOIN
键上,并且 INCLUDE
需要是您正在检索但不排序的数据。
这个讨论错过了重要的一点:问题不在于“非键列”是否更好地包含为索引列或包含列。
问题是使用包含机制来包含索引中不需要的列有多昂贵? (通常不是 where 子句的一部分,但通常包含在选择中)。所以你的困境总是:
单独在 id1、id2 ... idN 上使用索引或在 id1、id2 ... idN 上使用索引加上包括 col1、col2 ... colN
其中: id1, id2 ... idN 是经常用于限制的列,而 col1, col2 ... colN 是经常选择的列,但通常不用于限制
(将所有这些列作为索引键的一部分包含在内的选项总是很愚蠢(除非它们也用于限制) - 因为即使在“钥匙”没有改变)。
所以使用选项1或2?
回答:如果您的表很少更新 - 主要是插入/删除 - 那么使用包含机制来包含一些“热列”(通常用于选择 - 但不经常用于限制)相对便宜,因为插入/删除无论如何都需要更新/排序索引,因此在已经更新索引的同时存储一些额外的列几乎没有额外的开销。开销是用于在索引上存储冗余信息的额外内存和 CPU。
如果您考虑添加为包含列的列经常被更新(没有更新索引键列) - 或者 - 如果它们太多以至于索引变得接近表的副本 - 使用选项 1我建议!此外,如果添加某些包含列结果证明没有性能差异 - 您可能想跳过添加它们的想法:)验证它们是否有用!
键 (id1, id2 ... idN) 中每个相同值的平均行数也很重要。
请注意,如果在限制中使用了作为索引的包含列添加的列:只要可以使用这样的索引(基于对索引键列的限制),那么 SQL Server 匹配针对索引(叶节点值)的列限制,而不是围绕表本身采取昂贵的方式。
基本索引列已排序,但包含的列未排序。这节省了维护索引的资源,同时仍然可以在包含的列中提供数据以覆盖查询。因此,如果您想涵盖查询,您可以将搜索条件用于定位行到索引的已排序列中,然后“包含”具有非搜索数据的其他未排序列。它肯定有助于减少索引维护中的排序和碎片量。
原因(包括索引的叶级别中的数据)已经很好地解释了。您对此感到震惊的原因是,当您运行查询时,如果您没有包含附加列(SQL 2005 中的新功能),SQL Server 必须转到聚集索引以获取附加列这会花费更多时间,并在将新数据页加载到内存时为 SQL Server 服务、磁盘和内存(具体来说是缓冲区缓存)增加更多负载,从而可能会将其他更经常需要的数据从缓冲区缓存中推出。
如果您不需要键中的该列,首选 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
不是索引树的一部分,因此不用于缩小读取索引范围或对行进行排序。将 another_column
添加到索引键部分的末尾(在 col1
之后)是相当安全的。破坏任何东西的风险很小:
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
子句中。
如果将列添加到索引键会带来额外的好处(例如对于 order by
或者因为它可以缩小读取索引范围),请将其添加到键中。
您可以在此处阅读有关此内容的更长讨论:
https://use-the-index-luke.com/blog/2019-04/include-columns-in-btree-indexes
我在已经给出的答案中没有看到的另一个考虑因素是,包含的列可以是不允许作为索引键列的数据类型,例如 varchar(max)。
这允许您将此类列包含在覆盖索引中。我最近不得不这样做以提供一个 nHibernate 生成的查询,该查询在 SELECT 中有很多列,并带有一个有用的索引。
内联到索引定义中的所有列的总大小是有限制的。尽管如此,我从来不需要创建那么宽的索引。对我来说,更大的优势是您可以使用一个包含列的索引来覆盖更多查询,因为它们不必以任何特定的顺序定义。想想就是作为索引内的索引。一个例子是 StoreID(其中 StoreID 是低选择性,这意味着每个商店都与很多客户相关联),然后是客户人口统计数据(LastName、FirstName、DOB):如果您只是按此顺序内联这些列(StoreID、LastName , FirstName, DOB),您只能有效地搜索您知道 StoreID 和 LastName 的客户。
另一方面,在 StoreID 上定义索引并包括 LastName、FirstName、DOB 列实际上可以让您在 StoreID 上执行两个搜索——索引谓词,然后在任何包含的列上搜索谓词。这将让您涵盖所有可能的搜索排列,只要它以 StoreID 开头。
SELECT
的一部分,而对于某些不是?\