資料結構之B樹、B+樹、B*樹
1、應用背景
二叉查詢樹、AVL樹、紅黑樹等都屬於二叉樹的範圍,查詢的時間複雜度是O(log 2N),與樹的深度相關,那麼降低樹的深度自然會提高查詢效率。
但是我們面對這樣一個實際問題:大規模資料儲存中,樹節點儲存的元素數量是有限的(如果元素數量非常多的話,查詢就退化成節點內部的線性查找了),這樣導致二叉查詢樹結構由於樹的深度過大而造成磁碟I/O讀寫過於頻繁,進而導致查詢效率低下。
因此,為了減少磁碟I/O的次數,必須降低樹的深度,將“瘦高”的樹變得“矮胖”。一個基本的想法就是:
- 每個節點儲存多個元素
- 摒棄二叉樹結構,採用多叉樹
這樣就引出來了一個新的查詢樹結構 ——多路查詢樹。根據AVL樹給我們的啟發,一顆平衡多路查詢樹可以使得資料的查詢效率保證在O(logN)這樣的對數級別上。
1.1 機械硬碟結構
為什麼樹的深度會與磁碟I/O相關呢?這要從硬體層面來說明。
機械硬碟的結構圖如下:
硬碟中一般會有多個碟片組成,每個碟片包含兩個面,每個盤面都對應地有一個讀/寫磁頭。
每個盤面都被劃分為數目相等的磁軌,並從外緣的“0”開始編號,具有相同編號的磁軌形成一個圓柱,稱之為磁碟的柱面。
盤面中一圈圈灰色同心圓為一條條磁軌,從圓心向外畫直線,可以將磁軌劃分為若干個弧段,每個磁軌上一個弧段被稱之為一個扇區。扇區是磁碟的最小組成單元,通常是512位元組,不過由於不斷提高磁碟的大小,部分廠商設定每個扇區的大小是4096位元組。
順便多說一句,以上圖片是老式機械硬碟的示意圖,每個磁軌的扇區弧長是不一樣的。越靠內的磁軌密度越大,儲存的資料也就越多;越靠外的磁軌密度越小,儲存的資料也就越少。所以,雖然內外磁軌的扇區弧長不一樣,由於密度的原因,每個扇區儲存的資料量仍然是一樣的。然而在新式磁碟中,內外磁軌的扇區密度都是相同的,所以新式磁碟每個扇區的弧長都是一樣的。
1.2 讀寫原理
當硬碟驅動器執行讀/寫功能時,碟片繞主軸高速旋轉,當磁軌在磁頭)下通過時,就可以進行資料的讀 / 寫了。
系統將檔案儲存到磁碟上時,按柱面、磁頭、扇區的方式進行,即最先是第1磁軌的第一磁頭(也就是第1盤面的第1磁軌)下的所有扇區,然後,是同一柱面的下一磁頭,……,一個柱面儲存滿後就推進到下一個柱面,直到把檔案內容全部寫入磁碟。
系統也以相同的順序讀出資料。讀出資料時通過告訴磁碟控制器要讀出扇區所在的柱面號、磁頭號和扇區號(實體地址的三個組成部分)進行(目前多是通過LBA線性定址的方式定位)
由於儲存介質的特性,磁碟本身存取就比主存慢很多,再加上機械運動耗費(磁碟旋轉和磁頭移動),磁碟的存取速度往往是主存的幾百分之一,因此為了提高效率,要儘量減少磁碟I/O。為了達到這個目的,磁碟往往不是嚴格按需讀取,而是每次都會預讀
預讀的長度一般為頁(page)的整倍數。頁是計算機管理儲存器的邏輯塊,硬體及作業系統往往將主存和磁碟儲存區分割為連續的大小相等的塊,每個儲存塊稱為一頁(在許多作業系統中,頁得大小通常為4k),主存和磁碟以頁為單位交換資料。當程式要讀取的資料不在主存中時,會觸發一個缺頁異常,此時系統會向磁碟發出讀盤訊號,磁碟會找到資料的起始位置並向後連續讀取一頁或幾頁載入記憶體中,然後異常返回,程式繼續執行(詳情請參考頁面置換演算法以及虛擬記憶體)。
1.3 響應時間
磁碟讀取響應時間有三部分組成:
- 尋道時間:磁頭從開始移動到資料所在磁軌所需要的時間,尋道時間越短,I/O操作越快,目前磁碟的平均尋道時間一般在3-15ms,一般都在10ms左右,這部分的時間代價最高
- 旋轉延遲:碟片旋轉將請求資料所在扇區移至讀寫磁頭下方所需要的時間,旋轉延遲取決於磁碟轉速。普通硬碟一般都是7200rpm,慢的5400rpm,因此一般旋轉一圈大約8.3ms
- 資料傳輸時間:資料通過系統匯流排傳送到記憶體的時間, 一般傳輸一個位元組大概0.02us
從上面的指標來看,其實最重要的,或者說我們最關心的應該只有兩個:尋道時間與旋轉延遲。為提高磁碟傳輸效率,軟體層面應著重考慮減少尋道時間和延遲時間。
磁碟讀取資料是以盤塊(block)為基本單位的。位於同一盤塊中的所有資料都能被一次性全部讀取出來。而磁碟IO代價主要花費在查詢時間尋道時間上。因此我們應該儘量將相關資訊存放在同一盤塊,同一磁軌中。或者至少放在同一柱面或相鄰柱面上,以求在讀/寫資訊時儘量減少磁頭來回移動的次數,避免過多的尋道時間。
所以,在大規模資料儲存方面,大量資料儲存在硬碟中,而在硬碟中讀取/寫入塊(block)中某資料時,首先需要定位到磁碟中的某塊,如何有效地查詢硬碟中的資料,需要一種合理高效的外存資料結構,就是下面所要重點闡述的B樹以及相關變種B+樹及B*樹。
2、B樹
B樹(Balance Tree)由Rudolf Bayer, Edward M. McCreight於1970發表的一篇論文《Organization and Maintenance of Large Ordered Indices》中首次提出。
一棵m階的B 樹的特性如下:
- 根結點至少有兩個子女。
- 每個中間節點都包含k-1個元素和k個孩子,其中 m/2 <= k <= m
- 每一個葉子節點都包含k-1個元素,其中 m/2 <= k <= m
- 所有的葉子結點都位於同一層。
- 每個節點中的元素從小到大排列,節點當中k-1個元素正好是k個孩子包含的元素的值域分劃。
對比前面介紹過的2-3樹與2-3-4樹,可以發現,其實2-3樹就是m=3時的B樹,而2-3-4樹就是m=4時的B樹,兩者是B樹的特例。
注意:切勿簡單的認為一棵m階的B樹是m叉樹,雖然存在四叉樹,八叉樹,KD樹,以及/R樹/R*樹/等空間劃分樹,但與B樹完全不等同。
一個3階B樹的示意圖如下:
以上圖為例,若查詢的數值為5:
第一次磁碟I/O:在記憶體中定位(與17、35比較),比17小,左子樹;
第二次磁碟I/O:在記憶體中定位(與8、12比較),比8小,左子樹;
第三次磁碟I/O:在記憶體中定位(與3、5比較),找到5,終止。
整個過程中,我們可以看出:比較的次數並不比二叉查詢樹少,尤其適當某一節點中的資料很多時,但是磁碟IO的次數卻是大大減少。比較是在記憶體中進行的,相比於磁碟IO的速度,比較的耗時幾乎可以忽略。所以當樹的高度足夠低的話,就可以極大的提高效率。相比之下,節點中的元素多點也沒關係,僅僅是多了幾次記憶體互動而已,只要不超過磁碟頁的大小即可。
對於一顆節點為N,階為M的子樹,查詢和插入需要logM-1N ~ logM/2N次比較。這個很好證明,對於階為M的B樹,每一個節點的子節點個數為M/2 到 M-1之間,所以樹的高度在logM-1N至logM/2N之間。這種效率是很高的,對於N=62*1000000000個節點,如果度為1024,則logM/2N <=4,即在620億個元素中,如果這棵樹的度為1024,則只需要小於4次即可定位到該節點,然後再採用二分查詢即可找到要找的值。
B樹的查詢、插入等操作對與上一篇中的的2-3樹、2-3-4樹類似,不再贅述。
3、B+樹
B+樹是B樹的變形,與B樹的區別在於:
- 有k個子結點的結點必然有k個關鍵字
- 非葉結點僅具有索引作用,跟記錄有關的資訊均存放在葉結點中。
- 樹的所有葉結點構成一個有序連結串列,可以按照關鍵碼排序的次序遍歷全部記錄。
一棵3階B+樹如下所示:
B+樹的優勢在於查詢效率上,下面具體說明。
首先,B+樹的查詢和B樹一樣,類似於二叉查詢樹。起始於根節點,自頂向下遍歷樹,在節點內部典型的使用是二分查詢來確定這個位置。
不同的是:
(1)、B+樹中間節點沒有衛星資料(索引元素所指向的資料記錄),只有索引,而B樹每個結點中的每個關鍵字都有衛星資料;這就意味著同樣的大小的磁碟頁可以容納更多節點元素,在相同的資料量下,B+樹更加“矮胖”,I/O操作更少
B樹的衛星資料:
B+樹的衛星資料:
需要補充的是,在資料庫的聚集索引(Clustered Index)中,葉子節點直接包含衛星資料。在非聚集索引(NonClustered Index)中,葉子節點帶有指向衛星資料的指標。
(2)、其次,因為衛星資料的不同,導致查詢過程也不同;B樹的查詢只需找到匹配元素即可,最好情況下查詢到根節點,最壞情況下查詢到葉子結點,所說效能很不穩定,而B+樹每次必須查詢到葉子結點,效能穩定。
(3)、在範圍查詢方面,B+樹的優勢更加明顯。B樹的範圍查詢需要不斷依賴中序遍歷。首先二分查詢到範圍下限,在不斷通過中序遍歷,知道查詢到範圍的上限即可。整個過程比較耗時。
而B+樹的範圍查詢則簡單了許多。首先通過二分查詢,找到範圍下限,然後同過葉子結點的連結串列順序遍歷,直至找到上限即可,整個過程簡單許多,效率也比較高。
例如:同樣查詢範圍[3-11],兩者的查詢過程如下:
B樹的查詢過程:
B+樹的查詢過程:
4、 B*樹
B是B+樹的變體,在B+樹的非根和非葉子結點再增加指向兄弟的指標;
一棵3階B樹如下所示:
B*樹定義了非葉子結點關鍵字個數至少為(2/3)*M,即塊的最低使用率為2/3。