B+樹是爲磁盤和存儲工具設計的一種數據結構,它是一種平衡查找樹,它在查找,插入、修改方面的時間複雜度都穩定爲 O(logn)緩存
B+樹節點是一組按照key有序的元素,B+樹包含兩種類型的節點,一種是索引節點,一種是葉子節點數據結構
索引節點也叫內部節點,索引節點只包含key,不包含data, 節點的 key是升序排列的,對於指定的索引節點key來講,它左子樹上全部的key都小於它的key,它右子樹上全部的key都大於等於它的key工具
葉節點上存儲的是主鍵和數據(key和data), 全部的葉節點都在同一高度上,節點按key 從小到大而且經過指針使得彼此連接,這樣,全部的葉節點組成了一個雙向有序鏈表,葉節點這樣作的好處是在不訪問索引的狀況下能順序檢索數據,也能很好的支持範圍查詢的快處理設計
階數爲 m 的B+樹,每一個索引節點最多有 m 個子節點,每一個索引節點頁面最多存儲 m - 1 個索引key3d
全部索引節點的子節點數在 Math.ceil(m / 2) 和 m 之間指針
B +樹之因此稱爲平衡樹,是由於從根節點到葉節點的每條路徑都具備相同的長度。平衡樹意味着全部對單個值的搜索都須要從磁盤讀取相同數量的頁面。code
B+樹使用填充因子來控制頁面的分裂和合並,設置數據佔用頁面空間的百分比,目的是爲後面的數據預留一部分頁空間,當有新數據時,能夠放到預留的頁空間中,避免分頁的發生blog
默認的填充因子是50%,對於一棵m階的B+樹,填充因子是 m/2排序
B+樹經常使用操做涉及到查詢、插入、刪除、範圍查詢, 爲了便於說明,下面全部操做的例子中的B+樹無特殊說明都是5階樹索引
每一個頁面最多有4個key,大於等於5個時就須要分裂或者旋轉合併
填充因子默認是50%,頁面中已經使用了的數量爲2表示填充因子爲50%,同理,小於2時候表示填充因子小於50%,大於2時候表示填充因子大於50%
B+樹的索引節點是有序的,查詢單個key的話直接用二分查找定位到目標葉子頁面,在目標葉子頁面中順序遍歷,找到目標key,則返回葉子頁面中目標key對應的數據
B+樹的插入操做完成之後,有如下幾種狀況
這種狀況插入操做步驟最少,根據key把數據插入到葉節點已經排序的位置上便可,下圖中是插入key爲 23的數據,23會插入到包含 15, 21
的葉子頁面中,插入以後葉子頁面沒有滿,不用處理頁面分裂的狀況
步驟: (1):葉節點頁面分裂成兩個頁面 (2): 把中間行數據的key按順序加入到上一層的索引頁中 (3): 全部小於中間行數據key的數據放到左葉子頁面 (4): 全部大於等於中間行數據key的數據放到右葉子葉面
往圖(2) 的B+樹中插入key爲28的數據,這條數據會插入到包含15,21,23,27
的葉子頁面中,插入以後,該頁面數據已滿,必需要分裂成以下所示的兩個頁面:
左葉子頁面 | 右葉子頁面 |
---|---|
15,21 | 23,27,28 |
中間行數據key爲:23,放到上一層的索引頁面中15的後面,下面圖(3)是插入key爲28的結果
步驟: (1):葉節點頁面分裂成左右兩個葉子頁面 (2): 把中間行數據的key按順序加入到索引頁中 (3): 全部小於中間行數據key的數據放到左葉子頁面 (4): 全部大於等於中間行數據key的數據放到右葉子頁面 (5): 上面步驟(2)執行以後,索引頁面滿了,分裂成左右兩個索引頁 (6): 全部小於索引頁中間的key的放到左邊索引頁 (7): 全部大於索引頁中間的key的放到右邊索引頁 (8): 把索引頁中間的key放到更高一層的索引頁 若是步驟(8)執行以後,更高一層的索引頁滿了,繼續執行(5)-(8)步驟
圖(4) 的B+樹插入key爲30的數據,這條數據會插入到 23, 27, 28, 29
的葉子頁面中,插入以後,該頁面數據已滿,必需要分裂成以下所示的兩個頁面:
左葉子頁面 | 右葉子頁面 |
---|---|
23,27 | 28,29,30 |
中間行數據key爲:28,放到索引頁面中23 的後面
28 放到索引頁面23的後面以後,索引頁變成了4, 7, 15, 23, 28
, 這時索引頁也滿了,分裂成以下所示的三個頁面 :
左索引頁 | 右索引頁 | 更高一層索引頁 |
---|---|---|
4,7 | 23,28 | 15 |
下面圖(5)爲插入key爲30數據以後,葉子頁面和索引頁面分裂以後的結果:
B+樹的插入操做會有頁面分裂的狀況,頁面分裂就會有產生磁盤IO,相對內存,磁盤 IO 要慢得多,因此爲了減小磁盤IO操做,就要儘量的減小頁面分裂,充分利用頁面空間,所以B+樹提供了旋轉操做
旋轉操做的應用場景: B+樹葉子頁面空間已經滿了,可是它的左右兄弟頁面沒有滿
葉子頁面空間滿了,B+樹會優先檢查左右兄弟葉子頁面是否能容納數據,當左右兄弟頁面空間都滿了時,纔會考慮頁面分裂
圖(6)中,插入key爲12的數據,葉子頁面空間滿了,這時B+樹先檢查左兄弟頁面是否有多餘的空間,經過旋轉,把key分別爲 7, 10, 11, 12, 13
的葉子中的 7 移動到左兄弟頁面中,移動完成以後,左兄弟的key變成了 2, 5, 7
同時,葉子中key爲7的數據在上層索引頁中也有記錄,因此須要把上層索引頁中key爲7修改成 10,修改以後上層索引key分別爲 10, 15
,最終的結果以下圖(7)所示
葉子頁面插入新數據以後,頁面空間已滿,本來頁面時須要分裂的,可是經過把當前頁面上的數據移動到能容納數據的兄弟頁面中,減小了一次頁分裂,也即減小了一次磁盤IO操做
B+樹的刪除操做完成之後,有如下幾種狀況
這種狀況直接刪除節點,頁面會把刪除節點的位置標記爲空,以便存放後續其餘的數據,同時,若是刪除的key出如今上層的索引頁面中,須要用葉子頁面中被刪除節點的下一個節點key去替換它
圖(8)中 7是待刪除的節點,刪除7後,葉子頁面填充因子恰好等於50%,由於被刪除的7在上層的索引頁面中出現了相同的key,因此須要用葉子頁面中下一個key,也就是12替換上層索引頁面中的7,最終的結果以下面圖(9)所示:
葉子頁面填充因子小於50%的時候,爲了維持B+樹的平衡,會有頁面數據轉移和合並的操做
當一個葉子頁面填充因子小於50%,左右兄弟頁面存在填充因子大於50%的時候,能夠把兄弟頁面中的數據轉移到當前頁面中,上一層索引頁面中因葉子頁面數據轉移受影響的索引key也須要作相應的處理
若是左右兄弟頁面的填充因子都大於50%時,轉移任何一邊頁面數據到當前頁面均可以,雖然選擇不一樣的頁面轉移數據後,B+樹的形態不同,可是最終都是知足B+樹特色的
上面圖(10)中,刪除key爲16的數據 ( 圖中紅色標識的區域 ),刪除以後,原來key爲15, 16
的葉子頁面變成了 15
,頁面只剩下一個key
此時頁面的填充因子小於50%,左兄弟頁面填充因子大於50%,知足頁面數據轉移的條件
把左兄弟頁面 (key爲 7, 12, 13
)中的 13 轉移到當前頁面中
轉移以後,兩個頁面key數量恰好等於填充因子,左兄弟頁面key變爲 7, 12
,當前頁面的key變爲 13, 15
當前頁面中最小key值由原來的 15 變成了 13,爲了保持B+數的平衡,須要把當前頁面上一層的索引頁面中key爲15替換爲13, 最終的結果以下面圖(11)所示 :
上面說明了從兄弟頁面轉移數據到當前頁面,如今咱們來看下當前頁面數據量小於填充因子的時候,如何合併到兄弟頁面中
當一個葉子頁面填充因子小於50%,左右兄弟頁面存在填充因子等於50%的時候,能夠把這個葉子頁面合併到左右兄弟頁面中,上一層索引頁面中因葉子頁面數據合併受影響的索引key也須要作相應的處理
在圖(12)中,執行刪除key爲15的操做(圖中紅色區域),15位於key爲 13, 15
的頁面中,刪除15以後,當前頁面key變成了 13
, 只剩下一個key了
此時,當前頁面填充因子小於50%,左右兄弟節點填充因子等於50%,因此沒法從兄弟頁面轉移key數據到當前頁面,但知足當前頁面數據合併到兄弟頁面的條件
左右兄弟頁面都知足當前頁面數據合併過去,選擇任一兄弟頁面均可以,雖然選擇不一樣兄弟頁面,會致使B+樹的形態也不同,但最終都是讓B+樹維持平衡,這裏咱們選則合併到左兄弟頁面
15 被刪除了以後,當前頁面只剩下key爲 13
的數據了
它合併到左兄弟頁面以後, 當前頁面爲空,須要移除上一層索引頁面中指向當前頁面的索引key 13, 移除13的索引key以後, 索引頁面key由原來的 7, 13, 23
變成 7, 23
合併以後,左兄弟頁面key由原來的 7, 12
變成 7, 12, 13
最終的結果以下面 圖(13) 所示 :
當葉子頁面和索引頁面填充因子都小於50%的時候,葉子頁面和索引頁面都會有數據轉移或者合併的操做
在圖(14)中,執行刪除葉子頁面中key爲12的數據(圖中紅色區域),12 位於key爲 7, 12
葉子頁面中,刪除 12 以後,當前葉子頁面變成了 7
,只剩下一個key了
當前葉子頁面左右兄弟頁面填充因子都是50%,因此知足合併的條件,合併到左兄弟頁面或右兄弟頁面均可以,這裏咱們選擇合併到左兄弟頁面
當前葉子頁面中key爲 7 的數據合併到左兄弟頁面以後,當前葉子頁面沒數據了,而左兄弟頁面key變成了 2, 5, 7
爲了保持B+樹的平衡,指向當前葉子頁面的上一層索引頁面中,須要刪除key爲 7 的索引key, 刪除key爲7的索引後,索引頁面key變成了 15
, 這時該索引頁面填充因子小於50%,右兄弟頁面填充因子等於50%,知足合併的條件
可是,索引頁面數據合併到右兄弟頁面以後,根節點的左子樹就爲空了,爲了保持B+樹的平衡,根頁面數據須要合併到下一層的索引頁面中
最後的結果以下面圖(15)所示 :
B+樹的葉子節點是按照key從小到大的順序組成的一個雙向鏈表,因此B+樹很是適合範圍查詢(這裏說的範圍是B+樹中索引節點的key的範圍)
使用二分查找首先肯定範圍查詢的起始key所在的葉子節點的位置,而後順序遍歷葉節點鏈表,直到葉節點key大於範圍查詢結束key,查詢中止
一顆 m 階的B+樹,索引節點存儲的是索引信息,爲了計算方便,這裏假設一個索引key信息 8 字節,一個磁盤頁面大概 4K,那麼一個磁盤頁面能容納的索引數量爲: 4 * 1024 / 8 = 512
,此時 m 就等於 512
當B+樹高度爲2時,最多能容納 512 (512的1次方) 個索引信息
當B+樹高度爲3時,最多能容納 26萬 (512的2次方)個索引信息
當B+樹高度爲4時,最多能容納 1.3億 (512的3次方)個索引信息
當B+樹高度爲5時,最多能容納 687億 (512的4次方)個索引信息
從上面的數據能夠看到,B+樹高度爲5時,
能容納 687 億個索引信息,能夠很是夠用了
在實際的應用當中,B+樹的根節點都是緩存在內存中的,樹的最底層時葉子節點
因此針對高度爲5的B+樹,查找一條指定key值的數據最多隻須要3次磁盤IO就能定位到具體的葉子頁面,當樹高度爲4時,最多隻須要2次磁盤IO就能定位到具體的葉子頁面
B+樹主要用於磁盤和存儲工具,著名的MySQL引擎 InnoDB 索引的數據模型使用的就是 B+ 樹
當數據超過必定的量級的時候,爲了快速檢索數據而設置的索引信息也會變得很是龐大,並且這部分索引信息只能存儲在磁盤中,B+樹能從磁盤中快速檢索到須要的數據,而且時間複雜度穩定在O(logn)