索引深入淺出(3/10):聚集索引的B樹結構


在SQL Server里,有2種表是以存儲為基礎的。有聚集索引的表叫聚集表,沒有聚集索引的表叫堆表。在上一篇文章,我們討論了堆表的特性和存儲結構。在這篇文章里,我們來看下聚集表。

有聚集索引的表叫聚集表。聚集索引保存了使用B樹結構的聚集鍵,並只能以此順序存儲實際的數據。這也是SQL Server限制一個表只能有一個聚集索引,因為物理存儲順序只能有一個。我們來看看B樹結構的邏輯呈現。下圖是基於AdventureWorks2008R2數據庫,表SalesOrderDetail創建的。

1 USE IndexDB
2 GO
3 SELECT * INTO dbo.SalesOrderDetail FROM AdventureWorks2008R2.Sales.SalesOrderDetail
4 GO
5 CREATE UNIQUE CLUSTERED INDEX ix_SalesOrderDetail ON dbo.SalesOrderDetail(SalesOrderDetailID)

創建一個幫助表,並通過DBCC IND將表信息導入方便進一步分析。

 1 -- Create a helper table
 2 CREATE TABLE sp_table_pages
 3 (
 4   PageFID TINYINT, 
 5   PagePID INT,   
 6   IAMFID TINYINT, 
 7   IAMPID INT, 
 8   ObjectID INT,
 9   IndexID TINYINT,
10   PartitionNumber TINYINT,
11   PartitionID BIGINT,
12   iam_chain_type VARCHAR(30),    
13   PageType TINYINT, 
14   IndexLevel TINYINT,
15   NextPageFID TINYINT,
16   NextPagePID INT,
17   PrevPageFID INT,
18   PrevPagePID int, 
19   PRIMARY KEY (PageFID, PagePID)
20 )
21 GO
22 
23 -- Write everything in a table for further analysis
24 INSERT INTO sp_table_pages EXEC('DBCC IND(IndexDB,SalesOrderDetail,-1)')
25 GO

提取跟節點/索引頁,中間級/索引頁,葉子節點/數據頁信息。

 1 SELECT * FROM dbo.sp_table_pages WHERE IndexLevel=2 --根節點/索引頁
 2 DBCC TRACEON(3604)
 3 DBCC PAGE(IndexDB,1,90,3)
 4 
 5 SELECT * FROM dbo.sp_table_pages WHERE IndexLevel=1 --中間級/索引頁
 6 DBCC TRACEON(3604)
 7 DBCC PAGE(IndexDB,1,1864,3)
 8 DBCC TRACEON(3604)
 9 DBCC PAGE(IndexDB,1,1832,3)
10 DBCC TRACEON(3604)
11 DBCC PAGE(IndexDB,1,1808,3)
12 DBCC TRACEON(3604)
13 DBCC PAGE(IndexDB,1,1896,3)
14 
15 SELECT * FROM dbo.sp_table_pages WHERE IndexLevel=0 --葉子節點/數據頁
16 DBCC TRACEON(3604)
17 DBCC PAGE(IndexDB,1,1704,3)
18 DBCC TRACEON(3604)
19 DBCC PAGE(IndexDB,1,1720,3)
20 DBCC TRACEON(3604)
21 DBCC PAGE(IndexDB,1,1752,3)
22 DBCC TRACEON(3604)
23 DBCC PAGE(IndexDB,1,1784,3)

根據上述信息進行聚集索引結構示例圖繪制。

這張表有121317條記錄。SQL Server需要3層來存儲這個數據。我們根據上圖來分析下頁。在最高層,你可以看到只有一個頁,這個叫做根頁(root page)。在所有的B樹結構里,都只有一個根頁作為樹結構的訪問入口點。根層始終是最高層。在我們實例里根頁有第2層索引。在根層(root level)和中間級別(intermediate level)的頁叫索引頁。在索引頁里,SQL Server保存着聚集鍵(clustering key)和B樹下層的入口點(頁面指針)。聚集鍵保存的子頁id,最小值保存在下層頁(子頁)。在指定子頁上的聚集鍵最大值可以通過下一記錄找到。例如,在根層第一條記錄(Salesorderdetailid =NULL,pageid=1864),Salesorderdetailid小於等於30226可以在1864號頁找到。入口(Salesorderdetailid =30226,pageid=1832))表示,salesorderdetailid值在30226與60003之間的記錄可以在1832號頁找到,以此類推。在那層,上一頁和下一頁的值將做雙向鏈接來連接這些頁。在根層因為只有一個頁,所以上一頁和下一頁的值為0。

我們移到下一層來看,在這層有4個頁。你可以在下一頁和上一頁里找到值,也是用來鏈接那層的頁。這層被稱為中間層(intermediate level)。中間層的個數和中間層的頁數取決於表的大小和聚集鍵。一個大表可以有多個中間層,小表可能就沒有中間層。這層的值可以和根層一樣的方式讀取。例如,這層的第1頁的第一個入口(Salesorderdetailid =NULL,pageid=1704)表示,salesorderdetailid值小於等於72的可以在1704號頁找到。

下一層是底層,稱為葉子層(leaf level,index level 0)。在這層的頁被稱為葉子頁(leaf pages)或數據頁(data pages)。在這些頁里,你可以找到Salesorderdetail表記錄的全部數據(所有列)。換句話說,聚集索引的葉子層是實際數據存放的地方。

我們復制一張沒有聚集索引的Salesorderdetail表。

1 SELECT * INTO dbo.SalesOrderDetailHeap FROM AdventureWorks2008R2.Sales.SalesOrderDetail
2 GO

我們來執行下列查詢,2個查詢都返回同樣的結果,這里我們更關注的是IO部分。

1 SET STATISTICS IO ON
2 GO
3 SELECT * FROM SalesOrderDetail WHERE SalesOrderDetailID =75
4 GO
5 SELECT * FROM SalesOrderDetailheap WHERE SalesOrderDetailID =75

IO統計信息如下顯示。有聚集索引的表,相比另一個表,邏輯讀對它來說可以忽略不計的。

我們來看看SQL如何使用聚集索引的3個邏輯讀取來拿到記錄的。首先我們需要找出聚集索引的根節點(root node),DBCC IND命令可以幫我們找到。

1 DBCC IND('IndexDB','SalesOrderDetail',1)

返回1501條記錄,包含IAM頁,一個索引頁,和1499個數據頁,部分結果顯示如下。

從輸出結果,我們可以知道90頁(page type 2)是根頁,這個頁是這個表的入口點(entry point)。我們用DBCC PAGE看下這個頁。

1 DBCC traceon(3604)
2 GO
3 DBCC page('IndexDB',1,90,3) 

SQL Server在子頁保存聚集鍵的最小值,它的頁號索引頁里。例如,1864號頁會有表salesorderdetailid列值小於等於30226的所有記錄。同樣,1832號頁會有表salesorderdetailid列值在30226與60003之間的所有記錄(30226可能在這2個頁都有)。我們查找這條記錄的salesorderdetailid值小於30226,所以這條記錄的所有信息可以在子頁1864找到。

我們用DBCC PAGE看下這個1864頁。

1 DBCC traceon(3604)
2 GO
3 DBCC page('IndexDB',1,1864,3) 

輸出結果包含410條記錄,下面是部分結果顯示。你可以參數1(最后一個參數)來運行DBCC PAGE命令來看頁頭。那樣我們可以找到m_type=2的索引頁。用我們剛才描述的方法,我們知道要找的記錄(salesorderdetailid=75)可以在子頁1705里找到。

我們看下1704頁的內容:

1 DBCC traceon(3604)
2 GO
3 DBCC page('IndexDB',1,1705,3)  with tableresults

從頁頭部分,我們可以看到m_type是1,因此這個頁是數據頁,且是索引的葉子層。我們把如下輸出結果一直往下翻。我們就有記錄所有列的值,就是聚集索引葉子層,即實際的數據。

SQL Server從聚集索引只讀取3頁(根頁,中間層的1頁,還有葉子節點的1頁,即數據頁)就找到了我們的記錄。

我們用DBCC IND命令比較下2個表的區別(SalesOrderDetail,SalesOrderDetailHeap)

1 DBCC IND('IndexDB','SalesOrderDetail',1)
2 DBCC IND('IndexDB','SalesOrderDetailHeap',1)

SalesOrderDetailHeap表是堆表,只有1496個頁;SalesOrderDetail表是聚集表,包含一個聚集索引,卻有1501個頁。這個多出的5頁用來存儲B樹結構的中間級(intermediate)和根級(root)的索引頁。我們當他是聚集索引的優點吧,多用5個頁,卻將邏輯讀減少的只有3次。這個存儲開銷還是很划算的。 

參考文章:

http://www.sqlservercentral.com/blogs/practicalsqldba/2013/03/12/sql-server-index-part-3-explaining-the-clustered-table-structure/


免責聲明!

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



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