SQL Server索引進階第十篇:索引的內部結構

    在前一系列文章中我們着重講述了有關索引各種比較虛的概念,比如索引可以做什麼,索引的邏輯結構,接下來是時候來講述比較實在的東西了,也就是索引的物理結構。理解索引的內部結構對於整體的理解索引是至關重要的,只有理解了索引的內部結構以及SQL Server是如何維護索引的,你才能理解數據插入,刪除,更新,索引的創建、修改、刪除所帶來的成本。 

葉子層級和非葉子層級    

    所有的索引都是由葉子層級和非葉子層級組成的。前面的文章主要關注了索引的葉子層級。對於聚集索引來說,葉子節點就是索引本身,每一個葉子節點所包含的條目其實就是表中的行。對於非聚集索引來說,葉子節點每一個葉子包含的一行就是一個條目。每一個條目由索引鍵列,可選的包含列以及書籤構成,而書籤又由聚集索引鍵或RID構成。                無論索引中條目是來自錶行(聚集索引葉子節點),指向錶行(非聚集索引葉子節點)或是指向低層級的節點(非葉子節點),索引條目都可以被稱爲索引行。   
     非葉子節點是葉子節點層級的上層,SQL Server使用非葉子節點來:




  •     使得索引按照索引鍵聚集有序 
  •     根據索引鍵快速找到葉子節點 
    在本系列第一篇文章中,我們使用電話本的類比來解釋爲什麼索引能夠帶來性能的提升。用戶知道電話本是按照姓氏進行排序的,因此如果需要找”Meyer, Helen”根據首字母M知道這個人大概在電話本的中間位置,用戶直接翻開大約一半電話本開始查找。但對於SQL Server來說,可並不知道什麼是按字母表排序,也不知道哪一些頁是所謂的中間頁,除非把索引中的所有頁掃描一遍。爲了不用掃描所有的頁來找到所需條目,SQL Server爲在葉子節點之上增加了額外的頁。

非葉子層級  

    這些額外的頁也就是所謂的非葉子節點,或被稱爲索引的節點層級,是建立在葉子層級之上的層級。非葉子層級的作用是使得SQL Server對於特定的索引進行查找時,不僅有了統一的入口頁,並且不再需要掃描所有的頁。           
     在索引中的所有頁,無論哪個層級,都包含索引條目。正如文章中不斷重複說的,對於聚集索引來說,葉子節點的條目包含實際的行,所以如果一個表中包含了10億行,那麼葉子節點包含了10億個條目。    在葉子節點之上的層級,也就是非葉子節點的最底層,非葉子節點的最底層每一個索引條目都指向葉子節點。如果說表中每一頁能容納100個條目,那麼剛纔的十億行需要1000000000/100=1000萬個頁,與之對應的是,那麼最底層的非葉子節點就包含了1000萬的條目,也就是分佈在1000萬/100=10萬個頁中。(譯者注:原作者這裏沒說全,通常來說非葉子節點只包含索引鍵,因此每個條目的大小會遠遠小於葉子節點的條目大小,因此每頁可以容納更多的行,所以這裏非葉子節點應該遠遠小於10W個頁,後面的段落我們先不管這個,還是按照10W個頁算)。   

     再上一層的非葉子節點包含了指向這10萬個頁的條目,也就是10萬個條目,這10W個索引條目分佈在10萬/1000=1000頁中。根據這個規律,我們知道再上一層包含10個頁,直至最上層的節點只有一個頁了。
   
     索引中最上層的節點被稱爲根頁。剩下的除了根頁和葉子之外的層級就是所謂的中間層級。層級的編號是從葉子節點以0開始向上增長的,因此中間層級是以1開始的。    非葉子節點僅僅包含索引鍵,對於擁有包含列的索引來說,包含列僅僅存在於葉子節點。
    
    索引中的頁,除了根頁之外,都含有兩個額外的指針,分別指向按照索引順序當前頁之前和之後的頁。這種雙向鏈表結構使得SQL Server在索引掃描的時候更加有效。

一個簡單的例子   

    讓我們通過一個簡單的圖示來真正理解索引的內部結構吧,如下圖1所示。我們在Personnel.Employee表上創建了一個非聚集索引,代碼如下:
  1.   CREATE NONCLUSTERED INDEX IX_Full_Name ON Personnel.Employee ( LastName, FirstName, ) GO  
複製代碼
圖例註釋:     指向頁的指針包含了文件號和頁號。比如說5:4567指向的就是第5個文件的4567個頁。

圖1.索引的豎切圖     
    值得說明的是,上面的圖只是一個樣子,正常的情況下一個頁中會包含遠多於上面例子的行,並且頁也會遠遠多於上面的例子。    
    實際在頁中索引條目並不是有序的,而是靠偏移指針進行定位的,這個頁尾的偏移表是有序的。    
    很多情況下,頁中並不像上面圖中所展現的那樣,頁之間物理上是連續的,但它們之間邏輯上是連續的,邏輯和物理上的差異被稱之爲碎片。   
     正如我們之前所說,每一個索引可以包含不止一層的中間頁。    
    繼續使用我們之前電話本的類比。比如你查找名爲Helen Meyer的聯繫人,打開電話本找到第一頁,對於在區間 “Fernandez, Zelda”和 “Olsen, Karl”之間的名字,去看頁5:431.然後你找到431頁,這頁告訴你對於Kumar, Kevin”和“Nara, Alison”之間的名字,去找頁5:2006。然後你找到5:2006就找到了你所需的聯繫人。 

索引深度    

    索引的根頁以及相關信息是存在系統表中的。每當SQL Server進行頁查找時,SQL Server都會從根頁開始查找,經過中間節點,直到找到葉子節點,然後從葉子中找到需要的索引條目。對於我們10億行的表來說,從根節點到葉子節點共需要讀取5層。而對圖1所示的節點來說,只需要讀取3次IO。    
    上面所說的層數,也被成爲索引深度。取決於索引鍵的大小和數量。在AdventureWorks示例數據庫中,沒有哪個索引的層級超過3層。但對於其它索引鍵寬或是數據量大的表,就會有更深的層級。    sys.dm_db_index_physical_stats函數可以展示索引的詳細信息,深度和大小。這是一個表值函數,比如下面代碼我們可以找到SalesOrderDetai表相關的索引信息
  1. SELECT  OBJECT_NAME(P.OBJECT_ID) AS 'Table' , 
  2.         I.name AS 'Index' , 
  3.         P.index_id AS 'IndexID' , 
  4.         P.index_type_desc , 
  5.         P.index_depth , 
  6.         P.page_count 
  7. FROM    sys.dm_db_index_physical_stats(DB_ID(), 
  8.                                        OBJECT_ID('Sales.SalesOrderDetail'), 
  9.                                        NULL, NULL, NULL) P 
  10.         JOIN sys.indexes I ON I.OBJECT_ID = P.OBJECT_ID 
  11.                               AND I.index_id = P.index_id ;
複製代碼
得到的結果如圖2所示。


圖2.查詢sys.dm_db_index_physical_stats函數得到的結果 
    
    通過如下代碼我們可以看到更詳細的層級信息.SELECT  OBJECT_NAME(P.OBJECT_ID) AS 'Table' ,
  1.         I.name AS 'Index' , 
  2.         P.index_id AS 'IndexID' , 
  3.         P.index_type_desc , 
  4.         P.index_level , 
  5.         P.page_count 
  6. FROM    sys.dm_db_index_physical_stats(DB_ID(), 
  7.                                        OBJECT_ID('Sales.SalesOrderDetail'), 2, 
  8.                                        NULL, 'DETAILED') P 
  9.         JOIN sys.indexes I ON I.OBJECT_ID = P.OBJECT_ID 
  10.                               AND I.index_id = P.index_id ; 
複製代碼
得到的結果如圖3所示。


圖3.查詢索引的詳細信息通過圖3所示結果,可以看出




  •     葉子節點的條目分佈在407頁中
  •     中間節點僅僅需要2頁
  •     根節點只有1頁
    根據索引鍵的選擇,書籤的大小的不同,葉子節點通常是非葉子節點大小的上百倍。根據具體的數據不同而不同。     
    記住包含列僅僅適用在非聚集索引並且只存在於葉子節點中,包含列對於上層的層級是透明的,這也是爲什麼包含列不會增加非葉子節點鍵的大小。     
    因爲聚集索引的葉子節點是表數據本身,所以除了葉子節點的數據是表數據本身之外,還需要存儲一些額外的非葉子層級。因爲無論是否有聚集索引數據本身都是存在的,所以創建聚集索引的時候不僅需要花費一些時間和資源,創建成功後還需要一些額外的空間存儲非葉子節點。 

總結    

    索引的結構使得SQL Server可以根據鍵值快速找到所需的列,一旦找到所需的列之後,SQL Server可以:



  •     直接訪問所需的行
  •     從找到的數據位置開始,根據雙向鏈表找相鄰的頁
    索引樹結構早已經在沒有關係數據庫時就開始被使用了,事實證明,這是一種優秀的結構。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章