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


    索引設計是數據庫設計中比較重要的一個環節,對數據庫的性能其中至關重要的作用,但是索引的設計卻又不是那么容易的事情,性能也不是那么輕易就獲取到的,很多的技術人員因為不恰當的創建索引,最后使得其效果適得其反,可以說“成也索引,敗也索引”。

    本系列文章來自Stairway to SQL Server Indexes,翻譯和整理后發布在agilesharp和博客園,希望對廣大的技術朋友在如何使用索引上有所幫助

    在前一系列文章中我們着重講述了有關索引各種比較虛的概念,比如索引可以做什么,索引的邏輯結構,接下來是時候來講述比較實在的東西了,也就是索引的物理結構。理解索引的內部結構對於整體的理解索引是至關重要的,只有理解了索引的內部結構以及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表上創建了一個非聚集索引,代碼如下:

CREATE NONCLUSTERED INDEX IX_Full_Name ON Personnel.Employee ( LastName, 
FirstName, ) GO 

 

    圖例注釋:

     指向頁的指針包含了文件號和頁號。比如說5:4567指向的就是第5個文件的4567個頁。

    1001

圖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表相關的索引信息

 

SELECT  OBJECT_NAME(P.OBJECT_ID) AS 'Table' , 

        I.name AS 'Index' ,
        P.index_id AS 'IndexID' ,
        P.index_type_desc ,
        P.index_depth ,
        P.page_count
FROM    sys.dm_db_index_physical_stats(DB_ID(),
                                       OBJECT_ID('Sales.SalesOrderDetail'),
                                       NULL, NULL, NULL) P
        JOIN sys.indexes I ON I.OBJECT_ID = P.OBJECT_ID
                              AND I.index_id = P.index_id ;

 

得到的結果如圖2所示。

    index1002
圖2.查詢sys.dm_db_index_physical_stats函數得到的結果

 

通過如下代碼我們可以看到更詳細的層級信息.

SELECT  OBJECT_NAME(P.OBJECT_ID) AS 'Table' , 

        I.name AS 'Index' ,
        P.index_id AS 'IndexID' ,
        P.index_type_desc ,
        P.index_level ,
        P.page_count
FROM    sys.dm_db_index_physical_stats(DB_ID(),
                                       OBJECT_ID('Sales.SalesOrderDetail'), 2,
                                       NULL, 'DETAILED') P
        JOIN sys.indexes I ON I.OBJECT_ID = P.OBJECT_ID
                              AND I.index_id = P.index_id ;


得到的結果如圖3所示。

   1003

圖3.查詢索引的詳細信息

 

   通過圖3所示結果,可以看出

  •     葉子節點的條目分布在407頁中
  •     中間節點僅僅需要2頁
  •     根節點只有1頁

    根據索引鍵的選擇,書簽的大小的不同,葉子節點通常是非葉子節點大小的上百倍。根據具體的數據不同而不同。

     記住包含列僅僅適用在非聚集索引並且只存在於葉子節點中,包含列對於上層的層級是透明的,這也是為什么包含列不會增加非葉子節點鍵的大小。

     因為聚集索引的葉子節點是表數據本身,所以除了葉子節點的數據是表數據本身之外,還需要存儲一些額外的非葉子層級。因為無論是否有聚集索引數據本身都是存在的,所以創建聚集索引的時候不僅需要花費一些時間和資源,創建成功后還需要一些額外的空間存儲非葉子節點。

 

總結

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

  •     直接訪問所需的行
  •     從找到的數據位置開始,根據雙向鏈表找相鄰的頁

    索引樹結構早已經在沒有關系數據庫時就開始被使用了,事實證明,這是一種優秀的結構。


免責聲明!

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



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