SQL Server索引進階第五篇:索引包含列

包含列解析


所謂的包含列就是包含在非聚集索引中,並且不是索引列中的列。或者說的更通俗一點就是:把一些底層數據表的數據列包含在非聚集索引的索引頁中,而這些數據列又不是索引列,那麼這些列就是包含列。同時,這些包含列並不會對索引中的條目有影響。

好吧,爲了使得問題稍微清楚一點,我用個簡單的圖示說明一下:



我們可以用下面的語句在創建索引的時候加入包含列,代碼如下:

  1. CREATE NONCLUSTERED INDEX FK_ProductID_ ModifiedDate
  2. ON Sales.SalesOrderDetail (ProductID, ModifiedDate)
  3. INCLUDE (OrderQty, UnitPrice, LineTotal)
複製代碼

在上述的代碼中,ProductID和ModifiedDate包含在索引鍵中,而OrderQty, UnitPrice, LineTotal作爲包含列。

下面,我們就稍微深入到頁級別來看看建立索引前後的狀態。首先,我們看看,當建立非聚集索引,但是,索引中沒有包含列的時候,索引中的索引頁的詳細如下:

在上圖中可以看到,上面兩個索引頁是整個索引結構中的一部分,此時就包含了2個字段,而且這兩個字段都是索引鍵,另外一個Bookmark是指向底層數據表中數據行的一個指針。

下面,我們再來看看,我們建立了有包含列的非聚集索引之後,索引頁的情況,如下圖:

很明顯,原本的2個索引頁被拆分成爲了3個,因爲一部分底層數據行的數據的數據包含在了索引頁中。從這裏就可以知道一點:加入包含列到非聚集索引中,增大了索引結構中頁的個數,進而在使用的時候會佔用更多的磁盤空間和內存空間。


其實把一些列作爲包含列放在索引結構中就是一種用“空間換時間”的策略

這個時候,大家可能就會問了:“何必把列放在包含列中這麼麻煩,爲什麼不直接放在索引中?”。


其實把那三個列放在包含列而不是索引列中有以下幾個好處:


1.可以使得索引鍵變化引起的波動更小。舉個例子,如果索引列中的ProductID或者ModifiedDate發生變化,那麼索引結構就會要調整,重新定位到底層的數據行。但是,如果UnitPrice的值發生了變化,整個索引的結構不會發生變化,只是在包含列中的UnitPrice的值進行更新而已。

2.索引中的數據列越少,數據分佈的統計維護的成本就越小。

是否把一些作爲索引列還是包含了其實也和數據庫的類型和用途有很大的關係。例如在OLTP的數據庫中,有很多的數據的增刪改寫的操作,那麼建議索引中的列不要太多。如果是Warehouse類型的數據,那麼就以大量數據的讀取爲主,那麼可以考慮把很一些列包含在索引列中。


包含列實例演示一


下面通過是三個不同的小例子作爲比較演示,並且以AdventureWorks中的SalesOrderDetail示例數據表:

1.演示沒有非聚集索引的例子。

2.演示使用非聚集索引,但是沒有包含列的例子

3.演示有非聚集索引和包含列的例子

在講述的過程中,我們會結合實際的執行詳情說明問題。


示例一:沒有非聚集索引

執行語句如下:

  1. SET STATISTICS IO ON

  2. SELECT ProductID ,
  3. ModifiedDate ,
  4. SUM(OrderQty) AS 'No of Items' ,
  5. AVG(UnitPrice) 'Avg Price' ,
  6. SUM(LineTotal) 'Total Value'
  7. FROM Sales.SalesOrderDetail
  8. WHERE ProductID = 888
  9. GROUP BY ProductID ,
  10. ModifiedDate ;

  11. SET STATISTICS IO OFF
複製代碼


數據結果如下:


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

首先運行下面的語句,建立索引:

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


  5. CREATE NONCLUSTERED INDEX 
  6. FK_ProductID_ModifiedDateON Sales.SalesOrderDetail (ProductID, ModifiedDate)
複製代碼


然後再次運行示例中的查詢。


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

首先執行下面的代碼:

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

  5. CREATE NONCLUSTERED INDEX FK_ProductID_ModifiedDateON Sales.SalesOrderDetail 
  6. (ProductID, ModifiedDate) INCLUDE (OrderQty, UnitPrice, LineTotal) ;
複製代碼

然後再次運行之前的查詢。

好了,三個例子完成之後,我們就來比較一下結果,如下:

示例 1:No Nonclustered Index Table '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 Columns Table 'SalesOrderDetail'. Scan count 1, logical reads 3.Non read activity:  1%.


總結如下:

1.示例1中對整個表進行掃描,每一個行都要進行掃描,所以進行大量的IO活動。

2.例2首先使用非聚集索引找到ProductID的數據,然後通過書籤查找找到查詢中請求的其他的數據列。

3.示例3,只要在非聚集索引中查找需要的數據就行了,因爲查詢中所有列的數據都在裏面了,不用查找底層的數據表,所以查詢只是查找了索引結構,消耗的IO最少。

最後編輯yanyangtian 最後編輯於 2012-08-31 16:44:14
不是因爲有希望才堅持,而是因爲堅持纔有希望
如果您認可我們,請轉發一條微博:www.agilesharp.com提供靠譜的SQL Server性能優化,架構設計,解決方案
 
2#

下面,我們就來看看第二個示例。

包含列示例演示二

第二個查詢和第一個查詢類似,只是將Where條件語句改爲了按照ModifiedDate裏過濾。語句如下:

  1. SELECT ModifiedDate ,
  2. ProductID ,
  3. SUM(OrderQty) 'No of Items' ,
  4. AVG(UnitPrice) 'Avg Price' ,
  5. SUM(LineTotal) 'Total Value'
  6. FROM Sales.SalesOrderDetail
  7. WHERE ModifiedDate = '2003-10-01'
  8. GROUP BY ModifiedDate ,
  9. ProductID ;
複製代碼


查詢的結果如下顯示:



在查詢執行的過程中,通過Where條件,選擇出了1496條數據,最後通過Group語句,使得最後顯示的結果只有164行數據。

在三種不同的情況下,運行上面的查詢,結果比較如下:

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


我們可以知道,第一種情況和第二種情況下面的執行計劃是一樣的,都對整表進行掃描。而在第三種情況中,因爲此時的ModifiedDate已經包含在了索引的包含列中,此時就沒有對整表進行掃描,而是對非聚集索引結構進行掃描。所以,可以知道:如何把一些列作爲包含列放在索引中,那麼就可以在一定的程度上面提示效率,可以把原本需要整表掃描的操作,改爲非聚集索引掃描,這樣的成本更小。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章