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


在“索引深入淺出:非聚集索引的B樹結構在聚集表”里,我們討論了在聚集表上的非聚集索引,這篇文章我們討論下在堆表上的非聚集索引。

非聚集索引可以在聚集表或堆表上創建。當我們在聚集表上創建非聚集索引時,聚集索引鍵擔當為行指針。在堆表里,文件號,頁號和槽號(file id , page number and slot number)的組合在非聚集索引里擔當為行指針。

我們來看下手頭的一個例子。我們創建salesorderdetail表的副本,並在上面的productid和salesorderid 列創建創建非聚集索引。

1 DROP TABLE SalesOrderDetailHeap
2 
3 SELECT * INTO dbo.SalesOrderDetailHeap FROM AdventureWorks2008r2.Sales.SalesOrderDetail
4 GO
5 CREATE UNIQUE INDEX Ix_ProductId ON SalesOrderDetailHeap(ProductId,Salesorderid) 

收集非聚集索引相關信息:

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

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

現在我們來分析下SQL Server如何存儲堆表的非聚集索引,首先我們通過DBCC IND命令查看非聚集索引的頁分配情況,最后一個參數,2是Ix_ProductId的索引號。

1 DBCC ind('IndexDB','SalesOrderDetailHeap',2)

 

一共返回298條記錄,包括1個IAM頁,288個索引頁,我們用下列語句找下根層的頁號:

1 SELECT * FROM dbo.sp_table_pages ORDER BY IndexLevel DESC 

可以看到,indexlevel列最大值1的頁號是3270,這個頁就是根頁,因為indexlevel列最大值是1,所以這個堆表的非聚集索引的B樹結構只有2層,即根層和葉子層,也就是說288個索引頁中,1個頁是根層的根頁(也是索引頁),287個頁是葉子層的索引頁。我們來看看3270頁的信息。

1 DBCC TRACEON(3604)
2 DBCC PAGE(IndexDB,1,3720,3)

輸出結果,和聚集表里的非聚集索引的根頁結構是一樣的。

我們來看看葉子層的3608頁。

1 DBCC TRACEON(3604)
2 DBCC PAGE(IndexDB,1,3608,3)--葉子節點/索引頁

 在聚集表的非聚集索引的葉子層,聚集鍵與非聚集鍵一齊加入了葉子層的頁。這里我們沒有聚集索引,索引SQL Server加了個行標識號(8 bytes大小),由文件號(2 bytes),頁號(4 bytes)和槽號(2 bytes)組合而成。

從上圖我們可以清楚看出,productid值為707,salesorderid值為43665的記錄完整信息,可以在HeapRID 0xB800000001003E00位置找到。下面的查詢可以幫我們把RID轉為文件號:頁號:槽號(FileId:PageId:SlotNo)格式。

 

 1 DECLARE @HeapRid BINARY(8)
 2 SET @HeapRid = 0xB800000001003E00
 3 SELECT      
 4        CONVERT (VARCHAR(5),
 5                     CONVERT(INT, SUBSTRING(@HeapRid, 6, 1)
 6                                + SUBSTRING(@HeapRid, 5, 1)))
 7      + ':'
 8      + CONVERT(VARCHAR(10),
 9                     CONVERT(INT, SUBSTRING(@HeapRid, 4, 1)
10                                + SUBSTRING(@HeapRid, 3, 1)
11                                + SUBSTRING(@HeapRid, 2, 1)
12                                + SUBSTRING(@HeapRid, 1, 1)))
13      + ':'
14           + CONVERT(VARCHAR(5),
15                     CONVERT(INT, SUBSTRING(@HeapRid, 8, 1)
16                                + SUBSTRING(@HeapRid, 7, 1)))
17                                AS 'Fileid:Pageid:Slot' 

1:184:62表示文件號:1 ,頁號:184 ,槽號:62。我們來看看184頁。 

1 DBCC TRACEON(3604)
2 DBCC PAGE(IndexDB,1,184,3)       

從輸出我們可以看到,productid值為707,salesorderid值為43665的記錄所有列可以在槽號62找到,與1:184:62表示文件號:1 ,頁號:184 ,槽號:62完全一致。

我們通過下面的查詢看看SQL Server如何使用非聚集索引查找堆表上的數據,點擊工具欄的顯示包含實際的執行計划。

 

1 SET STATISTICS IO ON
2 GO
3 SELECT *  FROM SalesOrderDetailHeap WHERE productid=707 AND SalesOrderid=43665    

SQL Server需要進行2次I/O操作到達非聚集索引的葉子層,1次I/O操作通過使用RID查找(堆)拿到剩下的數據。執行計划如下所示:

即使我們將查詢語句修改為,只要 ProductId,SalesOrderid,SalesorderDetailId 這3列,SQL Server還是要進行鍵查找(Key lookup)操作。

1 SET STATISTICS IO ON
2 GO
3 SELECT *  FROM SalesOrderDetailHeap WHERE productid=707 AND SalesOrderid=43665    
4  
5 SET STATISTICS IO ON
6 GO
7 SELECT ProductId,SalesOrderid,SalesOrderDetailID FROM SalesOrderDetailHeap WHERE productid=707 AND SalesOrderid=43665       

這是因為,SalesorderDetailId列沒有定義為聚集鍵,在非聚集索引的葉子層沒有這列。為了避免鍵查找(key lookup)操作,我們需要將列限制到只有非聚集索引鍵(ProductKey ,salesorderid)。

1 SET STATISTICS IO ON
2 GO
3 SELECT ProductId,SalesOrderid FROM SalesOrderDetailHeap WHERE productid=707 AND SalesOrderid=43665  

如上圖所示,只有非聚集索引查找操作,沒有鍵查找(Key lookup)操作了。 

參考文章:

http://www.sqlservercentral.com/blogs/practicalsqldba/2013/03/17/sql-server-part-5-explaining-non-clustered-index-on-heap/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM