包含列的索引,有什么区别?

Indexes with included columns, what's the difference?

我从来没有真正理解这两个索引之间的区别,谁能解释一下区别是什么(性能方面,索引结构在数据库中的样子,存储方面等)?

包含索引

CREATE NONCLUSTERED INDEX IX_Address_PostalCode  
ON Person.Address (PostalCode) 
INCLUDE (AddressLine1, AddressLine2, City, StateProvinceID); 

'Normal'索引

CREATE NONCLUSTERED INDEX IX_Address_PostalCode  
ON Person.Address (PostalCode, AddressLine1, AddressLine2, City, StateProvinceID);

在第一个索引中,在Index page中只有PostalCode是键列,AddressLine1, AddressLine2, City, StateProvinceID是叶节点的一部分以避免key/RID查找

我更喜欢第一个索引,因为我的 table 将始终在 PostalCode 上过滤,并且任何此列 AddressLine1, AddressLine2, City, StateProvinceID 都将成为 select 的一部分而不是过滤

select AddressLine1, AddressLine2, City, StateProvinceID
from Person.Address 
Where PostalCode=  

在第二个索引中,在Index page中将有五个键列PostalCode, AddressLine1, AddressLine2, City, StateProvinceID

当我可以像

这样过滤数据时,我会更喜欢第二个索引
Where PostalCode = And AddressLine1 = 

Where PostalCode = And AddressLine2 = 

Where PostalCode = And AddressLine1  = and AddressLine2 = 

等等..

在任何情况下,索引中的第一列都应该是过滤的一部分以利用索引

在第一个示例中,只有索引列:PostalCode 存储在索引树中,所有其他列存储在索引的叶级中。这使得索引的大小更小,如果您不对其他列使用 where、Join、group by 而只对 PostalCode 使用 where、Join、group by,这将很有用。

在第二个索引中,所有列的所有数据都存储在索引树中,这使得索引更大,但如果您要使用 WHERE/JOIN/GROUP BY/ORDER 通过陈述。

包含列可以更快地检索在 select 列表中指定的数据。

例如,如果您是 运行:

SELECT PostalCode, AddressLine1, AddressLine2, City, StateProvinceID 
FROM Person.Address 
Where PostalCode= 'A1234'

这将受益于在 PostalCode 上创建索引并包括所有其他列

另一方面,如果您是 运行:

SELECT PostalCode, AddressLine1, AddressLine2, City, StateProvinceID 
FROM Person.Address 
Where PostalCode= 'A1234' or City = 'London' or StateProvinceID = 1 or AddressLine1 = 'street A' or AddressLine2 = 'StreetB'

将所有列都包含在索引中会受益更多

查看下面的链接,这些链接可能对您的查询有更多帮助

Index with Included Column: https://msdn.microsoft.com/en-us/library/ms190806(v=sql.105).aspx

Table and Index Organization: https://msdn.microsoft.com/en-us/library/ms189051(v=sql.105).aspx

索引的内部存储使用B-Tree结构,由"index pages"(根和所有中间页)和"index data pages"(仅叶页)组成。

Note do not confuse "index data pages" with the "data pages" (leaf pages of clustered indexes) which store most of the columns of actual data.

  • 只有索引列存储在索引页上。
  • 通过在 INCLUDE 部分放置一些列,每个页面上每个索引键存储的数据更少。
  • 意味着需要更少的页面来保存索引键。 (更容易将这些常用页面缓存在内存中更长时间。
  • 并且树中的级别可能更少。 (在这种情况下,性能优势会大得多,因为每个树级遍历都是另一个磁盘访问。

使用索引时,索引键用于通过索引页导航到正确的索引数据页。

  • 如果索引有 INCLUDE 列,该数据在查询需要时立即可用。
  • 如果查询需要的列在索引键或 INCLUDE 列中都不可用,则需要一个额外的 "bookmark lookup" 到聚簇索引(或堆,如果没有聚簇)中的正确行指数定义)。

一些注意事项希望能解决您的一些困惑:

  • 如果您的索引键和查询中的过滤器不够选择性,那么索引将被忽略(无论您的 INCLUDE 列中有什么) .
  • 您创建的每个索引都有 INSERT 和 UPDATE 语句的开销; "bigger" 索引更是如此。 (更大的值也适用于 INCLUDE 列。)
  • 因此,虽然理论上您可以创建大量包含列的大索引来匹配访问路径的所有排列:这将非常 counter-productive.

值得注意的是,在添加 INCLUDE 列作为功能之前:

  • 这是一种常见的索引调整 'trick',用于扩展索引的键以包含 index/filter 中不需要的列。 (称为覆盖索引。)
  • 输出列中通常需要这些列或作为连接到其他表的参考列。
  • 这将避免臭名昭著的 "bookmark lookups",但缺点是使索引 'wider' 超出了绝对必要。
  • 事实上,索引中较早的列通常已经识别出一个 唯一行 ,这意味着额外包含的列将完全多余如果不是为了 "avoiding bookmark lookups" 利益。
  • INCLUDE 列基本上可以更有效地实现相同的收益。

NB Something very important to point out. You generally get zero benefit out of INCLUDE columns in your indexes if you're in the lazy habit of always writing your queries as SELECT * .... By returning all columns you're basically ensuring a bookmark lookup is required in any case.