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

nle07wnf  于 2023-01-12  发布在  其他
关注(0)|答案(8)|浏览(536)

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

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

--或者--

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

INCLUDE子句对我来说是一个新的概念。为什么要使用它?在确定创建覆盖索引时是否使用INCLUDE子句时,您有什么建议?

z9smfwbn

z9smfwbn1#

如果列不在WHERE/JOIN/GROUP BY/ORDER BY中,而只在SELECT子句的列列表中,则使用INCLUDE
INCLUDE子句在最低/leaf级别添加数据,而不是在索引树中添加数据,这会使索引变小,因为它不是树的一部分
INCLUDE columns不是索引中的键列,所以它们没有排序。这意味着它对于 predicate 、排序等并没有真正的用处。但是,如果您在键列的几行中有一个剩余查找,它 * 可能 * 会很有用。
Another MSDN article with a worked example

jjhzyzn0

jjhzyzn02#

您可以使用INCLUDE将一个或多个列添加到非聚集索引的叶级,前提是这样做可以“覆盖”查询。
假设您需要查询雇员的ID、部门ID和姓氏。

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

如果你碰巧在(EmployeeID,DepartmentID)上有一个非聚集索引,一旦你找到了给定部门的雇员,你现在必须做“书签查找”来获得实际的完整雇员记录,仅仅是为了获得lastname列。如果你找到了很多雇员,这在性能方面会变得相当昂贵。
如果您在索引中包含该姓氏:

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

那么你所需要的所有信息都可以在非聚集索引的叶级中找到。只要在非聚集索引中查找并找到给定部门的雇员,你就拥有了所有必要的信息,并且不再需要对索引中找到的每个雇员进行书签查找--〉你保存了大量时间。
显然,您不可能在每个非聚集索引中包含每一列-但是如果您的查询只缺少一列或两列需要“覆盖”(并且经常使用),那么将它们INCLUDE到合适的非聚集索引中会非常有帮助。

ut6juiuv

ut6juiuv3#

这一讨论忽略了一个重要问题:问题不在于“非键列”是作为 index-列包含更好还是作为 included-列包含更好。
问题是使用include-mechanism来包含那些在index中并不真正需要的列(通常不是where-clause的一部分,但经常包含在select中),代价会有多大,所以你的困境总是:
1.对id 1、id 2... idN * 单独使用索引 * 或
1.对id 1、id 2... idN * 加上include* 列1、列2...列N使用索引
其中:id 1、id 2... idN是经常在限制中使用的列,col 1、col 2... colN是经常选择但通常 * 不 * 在限制中使用的列
(The将所有这些列作为索引键的一部分的选项总是很愚蠢(除非它们也用于限制)-因为即使“键”没有更改,索引也必须更新和排序,所以维护成本总是更高)。
那么使用选项1或2?
答:如果您的表很少更新--大多数是插入/删除--那么使用include机制来包含一些“热列”是相对便宜的(通常用于选择-但 * 不 * 通常用于限制),因为插入/删除无论如何都需要更新/排序索引,因此在更新索引的同时存储几个额外的列几乎没有额外的开销。开销是用于在索引上存储冗余信息的额外内存和CPU。
如果您考虑添加为包含列的列经常被更新(索引-* 键 -列没有被更新)- * 或 * -如果它们太多以至于索引变得接近您的表的副本-我建议使用选项1!另外,如果添加某些包含列没有带来性能差异-您可能不想添加它们:)验证它们是否有用!
键(id 1,id 2... idN)中每个相同值的平均行数也可能具有一定的重要性。
请注意,如果在 restriction 中使用了作为索引的 included-列添加的列:
只要索引本身可以使用 (基于对索引- 键 *-列的限制)-那么SQL Server将根据索引(叶节点值)匹配列限制,而不是绕过表本身进行代价高昂的操作。

j5fpnvbx

j5fpnvbx4#

基本索引列已排序,但包含列未排序。这样可以节省维护索引的资源,同时仍可以提供包含列中的数据以覆盖查询。因此,如果要覆盖查询,可以将查找行的搜索标准放入索引的已排序列中,但随后“包含”其他包含非搜索数据的未排序列。这无疑有助于减少索引维护中的排序和碎片量。

jum4pzuy

jum4pzuy5#

如果键中不需要该列,那么选择INCLUDE而不是键列的一个原因是文档,这使得将来索引的发展更加容易。
考虑您的示例:

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

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

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

当然,如果将列放在键部分可以获得额外的好处,就不应该将列放在INCLUDE中,下面的两个查询实际上更喜欢将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)

现在你知道了Col2Col3不是索引树的一部分,因此不用于缩小读取索引范围,也不用于对行进行排序。将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)

如果在Col1Col2之间添加another_col,会发生什么情况?其他查询会受到影响吗?
INCLUDE与键列相比还有其他一些"好处",如果添加这些列只是为了避免从表中获取它们的话,但是我认为文档方面是最重要的。
回答您的问题:
在确定是否创建包含INCLUDE子句的覆盖索引时,您有什么建议?
如果向索引添加列的唯一目的是使该列在索引中可用而不访问表,请将其放入INCLUDE子句中。
如果将列添加到索引键会带来额外的好处(例如,对于order by或因为它可以缩小读取索引范围),则将其添加到键。
您可以在这里阅读有关此问题的详细讨论:
https://use-the-index-luke.com/blog/2019-04/include-columns-in-btree-indexes

0mkxixxg

0mkxixxg6#

其原因(包括索引的叶级数据)已经被很好地解释了。你对此给予两次震动的原因是,当你运行查询时,如果你没有包括额外的列(SQL 2005中的新功能)SQL Server必须转到聚集索引以获取额外的列,这将花费更多时间,并给SQL Server服务增加更多负载,磁盘和存储器(具体地说是缓冲区高速缓存),因为新的数据页被加载到存储器中,从而潜在地将其它更经常需要的数据推出缓冲区高速缓存。

5m1hhzi4

5m1hhzi47#

在已经给出的答案中我没有看到的另一个注意事项是,包含列可以是不允许作为索引键列的数据类型,例如varchar(max)。
这允许您在覆盖索引中包含这样的列。我最近不得不这样做来提供一个nHibernate生成的查询,该查询在SELECT中有很多列,并具有有用的索引。

tjvv9vkg

tjvv9vkg8#

索引定义中所有列的总大小是有限制的。尽管如此,我从来没有创建过那么宽的索引。对我来说,更大的优点是你可以用一个包含列的索引来覆盖更多的查询,因为列不需要以任何特定的顺序定义。2考虑作为索引中的一个索引。3一个例子就是StoreID(其中StoreID是低选择性,意味着每个商店与许多顾客相关联),然后是顾客人口统计数据(姓氏、名字、出生日期):如果您只是按此顺序(StoreID、LastName、FirstName、DOB)内联这些列,则只能高效地搜索您知道StoreID和LastName的客户。
另一方面,在StoreID上定义索引并包含LastName、FirstName、DOB列,实质上允许您执行两次查找-在StoreID上定义索引 predicate ,然后在任何包含的列上查找 predicate 。这将允许您覆盖所有可能的搜索置换,只要它以StoreID开头。

相关问题