在参加70-433考试时,我发现你可以通过以下两种方式之一创建覆盖索引。
CREATE INDEX idx1 ON MyTable (Col1, Col2, Col3)
- 或 -
CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)
INCLUDE条款对我来说是新的。为什么要使用它以及在确定是否使用INCLUDE子句创建覆盖索引时会建议使用什么准则?
答案 0 :(得分:338)
如果该列不在WHERE/JOIN/GROUP BY/ORDER BY
中,而只在SELECT
子句的列列表中。
INCLUDE
子句在最低/叶级别添加数据,而不是在索引树中。
这使得索引更小,因为它不是树的一部分
INCLUDE columns
不是索引中的键列,因此不会对它们进行排序。
这意味着它对于谓词,排序等并不是真正有用,如上所述。但是,如果您在键列的几行中有残留查找,则可能
答案 1 :(得分:207)
您可以使用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)
然后,您需要的所有信息都可以在非聚集索引的叶级别中获得。只需在非聚集索引中查找并找到给定部门的员工,就可以获得所有必要的信息,并且不再需要在索引中找到的每个员工的书签查找 - >你节省了很多时间。
显然,你不能在每个非聚集索引中包含每一列 - 但是如果你确实有一些查询缺少一两列要被“覆盖”(并且经常使用),那么它会非常有用将这些包含到合适的非聚集索引中。
答案 2 :(得分:24)
这个讨论遗漏了重要的一点:问题不在于"非关键列"最好包含 index -columns或 included -columns。
问题是使用包含机制来包含在索引中真正不需要的列有多昂贵? (通常不是where子句的一部分,但通常包括在选择中)。所以你的困境永远是:
其中: id1,id2 ... idN是限制中经常使用的列,col1,col2 ... colN是经常选择的列,但通常不在限制中使用
(将所有这些列作为索引键的一部分包含在内的选项总是很愚蠢(除非它们也用于限制) - 因为索引必须更新和排序因此总是更昂贵即使"键"没有改变)。
那么使用选项1还是2?
答案:如果您的表很少更新 - 大多数是插入/删除 - 那么使用包含机制来包含一些"热列"相对便宜。 (通常用于选择 - 但不经常用于限制)因为插入/删除要求索引无论如何都要更新/排序,因此在存储一些额外的列时几乎没有额外的开销已经更新了索引。开销是用于在索引上存储冗余信息的额外内存和CPU。
如果您考虑添加为包含列的列经常更新(不更新index- 键 - 列) - 或 - 如果它太多了其中索引变得接近你的表副本 - 使用选项1我建议!此外,如果添加某些包含列的结果不会产生性能差异 - 您可能想要跳过添加它们的想法:)验证它们是否有用!
键中每个相同值的平均行数(id1,id2 ... idN)也很重要。
请注意,如果在限制中使用了一列(作为包含 - 列的索引) - 只要索引如此可以使用(基于对index- key -columns的限制) - 然后SQL Server将列限制与索引(叶节点值)匹配,而不是昂贵桌子周围的方式。
答案 3 :(得分:17)
基本索引列已排序,但包含的列未排序。这节省了维护索引的资源,同时仍然可以提供包含列中的数据以覆盖查询。因此,如果要覆盖查询,可以使用搜索条件将行定位到索引的已排序列中,然后使用非搜索数据“包含”其他未排序的列。它肯定有助于减少索引维护中的排序和碎片量。
答案 4 :(得分:6)
为什么(包括索引的叶级别中的数据)的原因得到了很好的解释。您对此提出两个动摇的原因是,当您运行查询时,如果您没有包含其他列(SQL 2005中的新功能),则SQL Server必须转到聚簇索引以获取其他列这需要更多的时间,并在新数据页面加载到内存时为SQL Server服务,磁盘和内存(特定的缓冲区缓存)增加更多负载,可能会将其他更常用的数据推出缓冲区缓存。
答案 5 :(得分:5)
我在已经给出的答案中没有看到的另一个考虑因素是,包含的列可以是不允许作为索引键列的数据类型,例如varchar(max)。
这允许您在覆盖索引中包含此类列。我最近不得不这样做来提供一个nHibernate生成的查询,它在SELECT中有很多列,并且有一个有用的索引。
答案 6 :(得分:2)
内联到索引定义中的所有列的总大小有限制。尽管如此,我从来没有创建过广泛的索引。 对我来说,更大的优势在于,您可以使用包含列的一个索引覆盖更多查询,因为它们不必按任何特定顺序定义。想想是作为索引中的索引。 一个例子是StoreID(其中StoreID是低选择性意味着每个商店与许多客户相关联),然后是客户人口统计数据(LastName,FirstName,DOB): 如果您按此顺序(StoreID,LastName,FirstName,DOB)内联这些列,则只能有效地搜索您知道StoreID和LastName的客户。
另一方面,在StoreID上定义索引并包括LastName,FirstName,DOB列,实际上可以让您在StoreID上执行两个seek-index谓词,然后在任何包含的列上查找谓词。这将允许您覆盖所有可能的搜索排列,只要它以StoreID开头。
答案 7 :(得分:0)
如果您不需要键中的该列,则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
我们假设情况并非如此,并且我们在col2
子句中使用了INCLUDE
,因为将其包含在索引的树部分中没有任何好处。
快进几年。
您需要调整此查询:
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
,那么您不知道在another_col
之后添加Col1
会破坏哪些查询。
CREATE INDEX idx1 ON MyTable (Col1, Col2, Col3)
如果在another_col
和Col1
之间添加Col2
,会发生什么情况?其他查询会受到影响吗?
INCLUDE
与键列相比还有其他“好处”。如果添加这些列只是为了避免从表中获取它们,则。但是,我认为文档方面是最重要的。
回答您的问题:
在确定是否创建包含或不包含INCLUDE子句的覆盖索引时,您会建议什么准则?
如果仅出于在不访问表的情况下使该列在索引中可用的目的而向索引添加一列,则将其放入INCLUDE
子句中。
如果将列添加到索引键会带来其他好处(例如,对于order by
或因为它可以缩小读取索引范围),请将其添加到键中。
您可以在此处阅读有关此内容的更长的讨论:
https://use-the-index-luke.com/blog/2019-04/include-columns-in-btree-indexes