葉子層級和非葉子層級
所有的索引都是由葉子層級和非葉子層級組成的。前面的文章主要關注了索引的葉子層級。對於聚集索引來說,葉子節點就是索引本身,每一個葉子節點所包含的條目其實就是表中的行。對於非聚集索引來說,葉子節點每一個葉子包含的一行就是一個條目。每一個條目由索引鍵列,可選的包含列以及書籤構成,而書籤又由聚集索引鍵或RID構成。 無論索引中條目是來自錶行(聚集索引葉子節點),指向錶行(非聚集索引葉子節點)或是指向低層級的節點(非葉子節點),索引條目都可以被稱爲索引行。
非葉子節點是葉子節點層級的上層,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表上創建了一個非聚集索引,代碼如下: 圖例註釋: 指向頁的指針包含了文件號和頁號。比如說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表相關的索引信息。 得到的結果如圖2所示。
圖2.查詢sys.dm_db_index_physical_stats函數得到的結果
通過如下代碼我們可以看到更詳細的層級信息.SELECT OBJECT_NAME(P.OBJECT_ID) AS 'Table' , 得到的結果如圖3所示。
圖3.查詢索引的詳細信息通過圖3所示結果,可以看出
-
葉子節點的條目分佈在407頁中
-
中間節點僅僅需要2頁
-
根節點只有1頁
記住包含列僅僅適用在非聚集索引並且只存在於葉子節點中,包含列對於上層的層級是透明的,這也是爲什麼包含列不會增加非葉子節點鍵的大小。
因爲聚集索引的葉子節點是表數據本身,所以除了葉子節點的數據是表數據本身之外,還需要存儲一些額外的非葉子層級。因爲無論是否有聚集索引數據本身都是存在的,所以創建聚集索引的時候不僅需要花費一些時間和資源,創建成功後還需要一些額外的空間存儲非葉子節點。
總結
索引的結構使得SQL Server可以根據鍵值快速找到所需的列,一旦找到所需的列之後,SQL Server可以:
-
直接訪問所需的行
-
從找到的數據位置開始,根據雙向鏈表找相鄰的頁