了解SQL Server中什么情况下适合用索引包含列,为什么不直接放在组合索引

包含列解析

所谓的包含列就是包含在非聚集索引中,并且不是索引列中的列。或者说的更通俗一点就是:把一些底层数据表的数据列包含在非聚集索引的索引页中,而这些数据列又不是索引列,那么这些列就是包含列。同时,这些包含列并不会对索引中的条目有影响。 好吧,为了使得问题稍微清楚一点,我用个简单的图示说明一下:

我们可以用下面的语句在创建索引的时候加入包含列,代码如下:

CREATE  NONCLUSTERED  INDEX  FK_ProductID_ ModifiedDate 
ON  Sales.SalesOrderDetail (ProductID, ModifiedDate) 
INCLUDE (OrderQty, UnitPrice, LineTotal)

 

上述的代码中,ProductID和ModifiedDate包含在索引键中,而OrderQty, UnitPrice, LineTotal作为包含列。

下面,我们就稍微深入到页级别来看看建立索引前后的状态。

1.  首先,我们看看,当建立非聚集索引,但是,索引中没有包含列的时候,索引中的索引页的详细如下

在上图中可以看到,上面两个索引页是整个索引结构中的一部分,此时就包含了2个字段,而且这两个字段都是索引键,另外一个Bookmark是指向底层数据表中数据行的一个指针。

2. 下面,我们再来看看,我们建立了有包含列的非聚集索引之后,索引页的情况,如下图

很明显,原本的2个索引页被拆分成为了3个,因为一部分底层数据行的数据的数据包含在了索引页中。从这里就可以知道一点:加入包含列到非聚集索引中,增大了索引结构中页的个数,进而在使用的时候会占用更多的磁盘空间和内存空间。

其实把一些列作为包含列放在索引结构中就是一种用“空间换时间”的策略

这个时候,大家可能就会问了:“何必把列放在包含列中这么麻烦,为什么不直接放在索引中?”。
其实把那三个列放在包含列而不是索引列中有以下几个好处:

  1. 可以使得索引键变化引起的波动更小。举个例子,如果索引列中的ProductID或者ModifiedDate发生变化,那么索引结构就会要调整,重新定 位到底层的数据行。但是,如果UnitPrice的值发生了变化,整个索引的结构不会发生变化,只是在包含列中的UnitPrice的值进行更新而已。
  2. 索引中的数据列越少,数据分布的统计维护的成本就越小。

是否把一些作为索引列还是包含了其实也和数据库的类型和用途有很大的关系。例如在OLTP的数据库中,有很多的数据的增删改写的操作,那么建议索引中的列不要太多。如果是Warehouse类型的数据,那么就以大量数据的读取为主,那么可以考虑把很一些列包含在索引列中。


包含列实例演示一

下面通过是三个不同的小例子作为比较演示,并且以AdventureWorks中的SalesOrderDetail示例数据表:
1.演示没有非聚集索引的例子。
2.演示使用非聚集索引,但是没有包含列的例子
3.演示有非聚集索引和包含列的例子

在讲述的过程中,我们会结合实际的执行详情说明问题。

示例1:没有非聚集索引

执行语句如下:

SET  STATISTICS  IO  ON   

SELECT  ProductID 
    , ModifiedDate
    , SUM (OrderQty)  AS  'No of Items'  
    , AVG (UnitPrice)  'Avg Price'  
    , SUM (LineTotal)  'Total Value'
FROM  Sales.SalesOrderDetail
WHERE  ProductID = 888
GROUP  BY  ProductID ,ModifiedDate ;

SET  STATISTICS  IO  OFF

数据结果如下: 

示例2:使用非聚集索引,但是没有包含列

首先运行下面的语句,建立索引:

IF EXISTS (  
    SELECT  1  
    FROM  sys.indexes 
    WHERE  name  =  'FK_ProductID_ModifiedDate'AND  OBJECT_ID = OBJECT_ID('Sales.SalesOrderDetail') 
) 
    DROP  INDEX  Sales.SalesOrderDetail.FK_ProductID_ModifiedDate 
       
CREATE  NONCLUSTERED  INDEXFK_ProductID_ModifiedDate 
ON Sales.SalesOrderDetail (ProductID, ModifiedDate)

然后再次运行示例中的查询。
 

示例3:有非聚集索引和包含列

首先执行下面的代码:

IF EXISTS (  
    SELECT  1  
    FROM  sys.indexes 
    WHERE  name  =  'FK_ProductID_ModifiedDate'AND  OBJECT_ID = OBJECT_ID('Sales.SalesOrderDetail') 
) 
    DROP  INDEX  Sales.SalesOrderDetail.FK_ProductID_ModifiedDate    

CREATE  NONCLUSTERED  INDEX  FK_ProductID_ModifiedDate 
ON Sales.SalesOrderDetail (ProductID, ModifiedDate) 
INCLUDE (OrderQty, UnitPrice, LineTotal) ;

然后再次运行之前的查询。


好了,三个例子完成之后,我们就来比较一下结果,如下:

示例 1:No Nonclustered IndexTable 'SalesOrderDetail'. Scan count 1, logical reads 1238.Non read activity:  8%.
示例2:Index – No Included Columns  Table 'SalesOrderDetail'. Scan count 1, logical reads 131.Non read activity:  0%.
示例3:With Included ColumnsTable 'SalesOrderDetail'. Scan count 1, logical reads 3.Non read activity:  1%.

总结如下:

  1. 示例1中对整个表进行扫描,每一个行都要进行扫描,所以进行大量的IO活动。
  2. 示例2首先使用非聚集索引找到ProductID的数据,然后通过书签查找找到查询中请求的其他的数据列。
  3. 示例3,只要在非聚集索引中查找需要的数据就行了,因为查询中所有列的数据都在里面了,不用查找底层的数据表,所以查询只是查找了索引结构,消耗的IO最少。

下面,我们就来看看第二个示例。

 

包含列实例演示二

第二个查询和第一个查询类似,只是将Where条件语句改为了按照ModifiedDate(索引键第二个列)里过滤。语句如下:

SELECT  
    ModifiedDate 
    ,ProductID 
    ,SUM (OrderQty)  'No of Items'  
    ,AVG (UnitPrice)  'Avg Price'  
    ,SUM (LineTotal)  'Total Value'
FROM  Sales.SalesOrderDetail
WHERE  ModifiedDate =  '2003-10-01'
GROUP  BY  ModifiedDate ,ProductID ;

查询的结果如下显示: 


在查询执行的过程中,通过Where条件,选择出了1496条数据,最后通过Group语句,使得最后显示的结果只有164行数据。
在三种不同的情况下,运行上面的查询,结果比较如下:

示例 1:No Nonclustered IndexTable 'SalesOrderDetail'. Scan count 1, logical reads 1238.Non read activity:  10%.
示例2:With Index – No Included ColumnsTable 'SalesOrderDetail'. Scan count 1, logical reads 1238.Non read activity:  10%.
示例3:With Included ColumnsTable 'SalesOrderDetail'. Scan count 1, logical reads 761.Non read activity:  8%.

我们可以知道,第一种情况和第二种情况下面的执行计划是一样的,都对整表进行扫描。而在第三种情况中,因为此时的ModifiedDate已经包含在了索引 的包含列中,此时就没有对整表进行扫描,而是对非聚集索引结构进行扫描。所以,可以知道:如何把一些列作为包含列放在索引中,那么就可以在一定的程度上面提高效率,可以把原本需要整表扫描的操作,改为非聚集索引扫描,这样的成本更小;另外,根据第一二种情况执行情况一样可以得知,组合索引键的顺序也决定着查询能否匹配利用到该索引,可以详见《给两个字段建立一个组合索引和分别建立单链索引有什么不同》中的解析。

 

(据追溯,本文针对包含列索引的讲解最早引用来自《Stairway to SQL Server Indexes》系列文章)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值