天天看點

索引深入淺出(4/10):非聚集索引的B樹結構在聚集表

一個表隻能有一個聚集索引,資料行以此聚集索引的順序進行存儲,一個表卻能有多個非聚集索引。我們已經讨論了聚集索引的結構,這篇我們會看下非聚集索引結構。

非聚集索引的邏輯呈現

簡單來說,非聚集索引是表的子集。當我們定義了一個非聚集索引時,SQL Server把整套非聚集索引鍵存在不同的頁裡。我們來看下一個包含BusinessEntityID(PK),PersonType,FirstName,LastName這4列的表,這個表上有一個非聚集索引定義。主體表按BusinessEntityID列(聚集索引鍵)的順序存儲。非聚集索引的存儲是與主體表分離的。如果你仔細看非聚集索引表,你會發現,記錄是按Firstname,lastname 列的順序排列的。簡單了解下,非聚集索引就是主體表的子集。

索引深入淺出(4/10):非聚集索引的B樹結構在聚集表

假設現在我們要找出first name值為Michael的記錄。如果你從實體表找的話,我們需要從頭到腳把每條記錄比對一次,因為記錄并沒有按first name列排序儲存。如果這個表記錄有上千條記錄的話,這将是一個非常無聊且費時的工作。如果我們在非聚集索引表裡找将會容易很多,因為這個表是按first name列以字母順序排列的。我們很容易定位到first name是Michael的記錄。我們并不需要再往下找,因為我們确定沒有更多的first name是Michael的記錄了。

現在我們得到了Firstname,lastname的值。那我們如何拿到其它2列的值?讓我們對非聚集索引做一些改動,将BusinessEntityID列也作為非聚集索引。

索引深入淺出(4/10):非聚集索引的B樹結構在聚集表

現在,一旦我們定位到記錄,我們可以使用BusinessEntityID(聚集索引鍵)列傳回主體表,得到其他列的值,這個操作被稱為書簽查找(bookmark lookups)或RID查找。

聚集索引與非聚集索引

 非聚集索引和聚集索引有一樣的B樹結構。非聚集索引鍵不會對主體表的資料排序做任何改變,因為聚集索引強制SQL Server将資料以聚集索引鍵的順序存儲。聚集索引的葉子層由包含表具體資料的資料頁組成,而非聚集索引的葉子層由索引頁組成。

非聚集索引可以定義在堆表或聚集表。在非聚集索引的葉子層,每個索引行包含非聚集索引鍵值和行定位器。這個定位器指向聚集索引或堆表的資料行。在非聚集索引行裡的行定位器要麼指向行,要麼指向行聚集索引鍵。如果是堆表,它沒有聚集索引,行定位器是個指向行的指針。這個指針由頁裡行的(檔案号:頁号:槽号,file identifier :page number :slot number)組成。整個指針被稱為ROW ID(RID)。如果表有聚集索引,行定位器是行的聚集索引鍵。

非聚集索引深入淺出

我們用文章“索引深入淺出:聚集索引的B樹結構”用到的salesorderdetails建立一個非聚集索引,這個表在salesorderdetailid列有一個聚集索引。

1 CREATE UNIQUE INDEX Ix_ProductId ON SalesOrderDetail(ProductId,Salesorderid)       

收集非聚集索引相關資訊:

1 TRUNCATE TABLE dbo.sp_table_pages
 2 INSERT INTO sp_table_pages EXEC('DBCC IND(IndexDB,SalesOrderDetail,2)')
 3 GO
 4 
 5 SELECT * FROM dbo.sp_table_pages ORDER BY IndexLevel DESC --根節點/索引頁
 6 DBCC TRACEON(3604)
 7 DBCC PAGE(IndexDB,1,3472,3)
 8 
 9 DBCC TRACEON(3604)
10 DBCC PAGE(IndexDB,1,3416,3)--葉子節點/索引頁
11 
12 DBCC TRACEON(3604)
13 DBCC PAGE(IndexDB,1,3557,3)--葉子節點/索引頁
14 SELECT * FROM dbo.sp_table_pages WHERE IndexLevel=0 --葉子節點/索引頁      

根據上述資訊進行非聚集索引邏輯示意圖的繪制:

索引深入淺出(4/10):非聚集索引的B樹結構在聚集表

現在我們來分析下SQL Server如何存儲非聚集索引,首先我們通過DBCC IND指令檢視非聚集索引的頁配置設定情況,最後一個參數,2是Ix_ProductId的索引号。

1 DBCC IND(IndexDB,SalesOrderDetail,2)      
索引深入淺出(4/10):非聚集索引的B樹結構在聚集表
索引深入淺出(4/10):非聚集索引的B樹結構在聚集表

 我們看到輸出結果一共有229條記錄,包含1個IAM頁和229個索引頁。我們可以通過找IndexLevel 列值最大的記錄,來找根頁(root page)。記住索引層級是從葉子層向根層增長的。

1 SELECT * FROM dbo.sp_table_pages ORDER BY IndexLevel DESC --根節點/索引頁      
索引深入淺出(4/10):非聚集索引的B樹結構在聚集表

在這個表裡,我們根層(root leve)頁号是3472,index level是1,這就是說,這個非聚集索引的B樹結構隻有根層(root level)和葉子層(leaf level),沒有中間層(intermediate level)。我們來看看3472頁。 

1 DBCC TRACEON(3604)
2 DBCC PAGE(IndexDB,1,3472,3)      
索引深入淺出(4/10):非聚集索引的B樹結構在聚集表

傳回結果一共有227條記錄(227個葉子層的索引頁)。部分結果如上所示。這和聚集索引裡的根層(root)/中間層(intermediate)的頁結構是一樣的。productid與salesorderid組合的值小于或等于(707,51151)的所有記錄,可以在子頁3416裡找到。productid與salesorderid組合的值在(707,51151)與(707,55920)之間的所有記錄,可以在子頁3417裡找到,并以此類推。

我們來看看3417頁。

1 DBCC TRACEON(3604)
2 DBCC PAGE(IndexDB,1,3417,3)      
索引深入淺出(4/10):非聚集索引的B樹結構在聚集表

一共傳回539條記錄,都是product id為707的記錄。這裡的索引隻用2層,這個是B樹結構的葉子層。你會注意到,這裡沒有子頁ID列,但我們有salesorderdetailid列(聚集索引鍵),SQL Server用它來進行鍵或書簽查找操作。

 我們來看看,SQL Server如何使用這個索引進行一個SELECT操作。點選工具欄的

索引深入淺出(4/10):非聚集索引的B樹結構在聚集表

顯示包含實際的執行計劃。

1 SET STATISTICS IO ON
2 GO
3 SELECT *  FROM SalesOrderDetail WHERE productid=707 AND SalesOrderid=51192       
索引深入淺出(4/10):非聚集索引的B樹結構在聚集表
索引深入淺出(4/10):非聚集索引的B樹結構在聚集表

可以看到執行計劃的鍵查找操作。因為這裡where條件剛好完全符合我們非聚集索引定義,SQL Server用這個索引來執行查詢。首先SQL Server讀取B樹結構的根頁。我們的查詢條件組合(707,51192)落在根頁的第二條記錄上,是以SQL Server走到它的子頁(頁号3417)。在這個頁裡,我們可以用條件組合(707,51192)定位到具體的記錄上,它的salesorderdetailid值是37793。從這裡開始,SQL Server使用salesorderdetailid值進行鍵查找(key look up)操作。從上一個文章知道,但我們進行任何聚集索引鍵查找是,需要執行3個I/O。 是以這裡,SQL Server需要執行5個I/O操作(2個在非聚集索引,3個在聚集索引的書簽/鍵查找(bookmark/key lookup),這個和你的結果輸出一緻。

索引深入淺出(4/10):非聚集索引的B樹結構在聚集表

為了更好的了解它,我們可以把非聚集索引當作salesorderdetail 表的一個子表(我們把它叫做Saleorderdetail_NC),有productid,salesorderid 和 SalesorderDetailid列,并且 ProductId與salesorderid列組合為聚集索引。上述查詢的結果可以通過以下2個查詢來獲得。

1 SELECT *  FROM SalesOrderDetail_nc WHERE productid=707 AND SalesOrderid=51192
2 GO
3 SELECT *  FROM SalesOrderDetail WHERE SalesOrderDetailid=37793      

我們再來看一個查詢:

1 SELECT *  FROM SalesOrderDetail WHERE productid=707      
索引深入淺出(4/10):非聚集索引的B樹結構在聚集表

查詢傳回3083條記錄,查詢條件與非聚集索引的第一列比對。但是SQL Server并沒用非聚集索引來執行這個查詢,查詢計劃如下所示。

索引深入淺出(4/10):非聚集索引的B樹結構在聚集表

這樣做的原因是,如果使用非聚集索引,就需要為3083條記錄執行書簽查找(key lookup)。這會産生9249個I/O操作(3083*3)。是以,SQL Server使用了聚集索引掃描,它隻需要1501(對于聚集索引樹結構需要的頁數)個I/O操作。如果我們做一個小的改動,隻要Productid ,SalesOrderDetailid和SalesOrderId列,SQL Server會使用非聚集索引,因為它不需要進行書簽查找(bookmark lookup)操作。非聚集索引的葉子層已經包含這些列了。

1 SELECT productid,salesorderdetailid,salesorderid  FROM SalesOrderDetail WHERE productid=707      
索引深入淺出(4/10):非聚集索引的B樹結構在聚集表

這篇文章真的有點長,而且我是該死的BING輸入法出錯,導緻浏覽器崩潰,丢失一個晚上3個小時成果,重新寫好的,希望大家看了之後可以透徹了解非聚集索引了,晚安各位!!2015-05-14 00:18:42 

參考文章:

http://www.sqlservercentral.com/blogs/practicalsqldba/2013/03/14/sql-server-part-4-explaining-the-non-clustered-index-structure-/

注:此文章為

WoodyTu

學習MS SQL技術,收集整理相關文檔撰寫,歡迎轉載,請在文章頁面明顯位置給出此文連結!

若您覺得這篇文章還不錯請點選下右下角的推薦,有了您的支援才能激發作者更大的寫作熱情,非常感謝!

繼續閱讀