前言 mysql
首先,爲何要總結B樹、B+樹的知識呢?最近在學習數據庫索引調優相關知識,數據庫系統廣泛採用B-/+Tree做爲索引結構(例如mysql的InnoDB引擎使用的B+樹),理解不透徹B樹,則沒法理解數據庫的索引機制;接下來將用最簡潔直白的內容來了解B樹、B+樹的數據結構算法
另外,B-樹,即爲B樹。由於B樹的原英文名稱爲B-tree,而國內不少人喜歡把B-tree譯做B-樹,其實,這是個很是很差的直譯,很容易讓人產生誤解。如人們可能會覺得B-樹是一種樹,而B樹又是一種樹。而事實上是,B-tree就是指的B樹,目前理解B的意思爲平衡sql
B樹的出現是爲了彌合不一樣的存儲級別之間的訪問速度上的巨大差別,實現高效的 I/O。平衡二叉樹的查找效率是很是高的,並能夠經過下降樹的深度來提升查找的效率。可是當數據量很是大,樹的存儲的元素數量是有限的,這樣會致使二叉查找樹結構因爲樹的深度過大而形成磁盤I/O讀寫過於頻繁,進而致使查詢效率低下。另外數據量過大會致使內存空間不夠容納平衡二叉樹全部結點的狀況。B樹是解決這個問題的很好的結構數據庫
概念數據結構
首先,B樹不要和二叉樹混淆,在計算機科學中,B樹是一種自平衡樹數據結構,它維護有序數據並容許以對數時間進行搜索,順序訪問,插入和刪除。B樹是二叉搜索樹的通常化,由於節點能夠有兩個以上的子節點。[1]與其餘自平衡二進制搜索樹不一樣,B樹很是適合讀取和寫入相對較大的數據塊(如光盤)的存儲系統。它一般用於數據庫和文件系統。性能
定義學習
B樹是一種平衡的多分樹,一般咱們說m階的B樹,它必須知足以下條件: 大數據
第一次看到這個定義的時候,在想什麼鬼?。。。。什麼是階?子節點、飛葉子點、根???啥意思!少年別慌。。。優化
什麼是B樹的階 ?spa
B樹中一個節點的子節點數目的最大值,用m表示,假如最大值爲10,則爲10階,如圖
全部節點中,節點【13,16,19】擁有的子節點數目最多,四個子節點(灰色節點),因此能夠定義上面的圖片爲4階B樹,如今懂什麼是階了吧
什麼是根節點 ?
節點【10】即爲根節點,特徵:根節點擁有的子節點數量的上限和內部節點相同,若是根節點不是樹中惟一節點的話,至少有倆個子節點(否則就變成單支了)。在m階B樹中(根節點非樹中惟一節點),那麼有關係式2<= M <=m,M爲子節點數量;包含的元素數量 1<= K <=m-1,K爲元素數量。
什麼是內部節點 ?
節點【13,16,19】、節點【3,6】都爲內部節點,特徵:內部節點是除葉子節點和根節點以外的全部節點,擁有父節點和子節點。假定m階B樹的內部節點的子節點數量爲M,則必定要符合(m/2)<= M <=m關係式,包含元素數量M-1;包含的元素數量 (m/2)-1<= K <=m-1,K爲元素數量。m/2向上取整。
什麼是葉子節點?
節點【1,2】、節點【11,12】等最後一層都爲葉子節點,葉子節點對元素的數量有相同的限制,可是沒有子節點,也沒有指向子節點的指針。特徵:在m階B樹中葉子節點的元素符合(m/2)-1<= K <=m-1。
好了,概念已經清楚,不用着急背公式, 接着往下看
插入
針對m階高度h的B樹,插入一個元素時,首先在B樹中是否存在,若是不存在,即在葉子結點處結束,而後在葉子結點中插入該新的元素。
上面三段話爲插入動做的核心,接下來以5階B樹爲例,詳細講解插入的動做;
5階B樹關鍵點:
插入8
圖(1)插入元素【8】後變爲圖(2),此時根節點元素個數爲5,不符合 1<=根節點元素個數<=4,進行分裂(真實狀況是先分裂,而後插入元素,這裏是爲了直觀而先插入元素,下面的操做都同樣,再也不贅述),取節點中間元素【7】,加入到父節點,左右分裂爲2個節點,如圖(3)
接着插入元素【5】,【11】,【17】時,不須要任何分裂操做,如圖(4)
插入元素【13】
節點元素超出最大數量,進行分裂,提取中間元素【13】,插入到父節點當中,如圖(6)
接着插入元素【6】,【12】,【20】,【23】時,不須要任何分裂操做,如圖(7)
插入【26】時,最右的葉子結點空間滿了,須要進行分裂操做,中間元素【20】上移到父節點中,注意經過上移中間元素,樹最終仍是保持平衡,分裂結果的結點存在2個關鍵字元素。
插入【4】時,致使最左邊的葉子結點被分裂,【4】剛好也是中間元素,上移到父節點中,而後元素【16】,【18】,【24】,【25】陸續插入不須要任何分裂操做
最後,當插入【19】時,含有【14】,【16】,【17】,【18】的結點須要分裂,把中間元素【17】上移到父節點中,可是狀況來了,父節點中空間已經滿了,因此也要進行分裂,將父節點中的中間元素【13】上移到新造成的根結點中,這樣具體插入操做的完成。
刪除
首先查找B樹中需刪除的元素,若是該元素在B樹中存在,則將該元素在其結點中進行刪除;刪除該元素後,首先判斷該元素是否有左右孩子結點,若是有,則上移孩子結點中的某相近元素(「左孩子最右邊的節點」或「右孩子最左邊的節點」)到父節點中,而後是移動以後的狀況;若是沒有,直接刪除。
接下來還以5階B樹爲例,詳細講解刪除的動做;
如圖依次刪除依次刪除【8】,【20】,【18】,【5】
首先刪除元素【8】,固然首先查找【8】,【8】在一個葉子結點中,刪除後該葉子結點元素個數爲2,符合B樹規則,操做很簡單,我們只須要移動【11】至原來【8】的位置,移動【12】至【11】的位置(也就是結點中刪除元素後面的元素向前移動)
下一步,刪除【20】,由於【20】沒有在葉子結點中,而是在中間結點中找到,我們發現他的繼承者【23】(字母升序的下個元素),將【23】上移到【20】的位置,而後將孩子結點中的【23】進行刪除,這裏剛好刪除後,該孩子結點中元素個數大於2,無需進行合併操做。
下一步刪除【18】,【18】在葉子結點中,可是該結點中元素數目爲2,刪除致使只有1個元素,已經小於最小元素數目2,而由前面咱們已經知道:若是其某個相鄰兄弟結點中比較豐滿(元素個數大於ceil(5/2)-1=2),則能夠向父結點借一個元素,而後將最豐滿的相鄰兄弟結點中上移最後或最前一個元素到父節點中,在這個實例中,右相鄰兄弟結點中比較豐滿(3個元素大於2),因此先向父節點借一個元素【23】下移到該葉子結點中,代替原來【19】的位置,【19】前移;然【24】在相鄰右兄弟結點中上移到父結點中,最後在相鄰右兄弟結點中刪除【24】,後面元素前移。
最後一步刪除【5】, 刪除後會致使不少問題,由於【5】所在的結點數目恰好達標,恰好知足最小元素個數(ceil(5/2)-1=2),而相鄰的兄弟結點也是一樣的狀況,刪除一個元素都不能知足條件,因此須要該節點與某相鄰兄弟結點進行合併操做;首先移動父結點中的元素(該元素在兩個須要合併的兩個結點元素之間)下移到其子結點中,而後將這兩個結點進行合併成一個結點。因此在該實例中,我們首先將父節點中的元素【4】下移到已經刪除【5】而只有【6】的結點中,而後將含有【4】和【6】的結點和含有【1】,【3】的相鄰兄弟結點進行合併成一個結點。
也許你認爲這樣刪除操做已經結束了,其實否則,在看看上圖,對於這種特殊狀況,你當即會發現父節點只包含一個元素【7】,沒達標(由於非根節點包括葉子結點的元素K必須知足於2=<K<=4,而此處的K=1),這是不可以接受的。若是這個問題結點的相鄰兄弟比較豐滿,則能夠向父結點借一個元素。而此時兄弟節點元素恰好爲2,剛剛知足,只能進行合併,而根結點中的惟一元素【13】下移到子結點,這樣,樹的高度減小一層。
看完插入,刪除,想必也把B樹的特徵掌握了,下面普及下其餘知識,換個腦子
計算機存儲設備通常分爲兩種:內存儲器(main memory)和外存儲器(external memory)。
內存儲器爲內存,內存存取速度快,但容量小,價格昂貴,並且不能長期保存數據(在不通電狀況下數據會消失)。
外存儲器即爲磁盤讀取,磁盤讀取數據靠的是機械運動,每次讀取數據花費的時間能夠分爲尋道時間、旋轉延遲、傳輸時間三個部分,尋道時間指的是磁臂移動到指定磁道所須要的時間,主流磁盤通常在5ms如下;旋轉延遲就是咱們常常據說的磁盤轉速,好比一個磁盤7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms;傳輸時間指的是從磁盤讀出或將數據寫入磁盤的時間,通常在零點幾毫秒,相對於前兩個時間能夠忽略不計。那麼訪問一次磁盤的時間,即一次磁盤IO的時間約等於5+4.17 = 9ms左右,聽起來還挺不錯的,但要知道一臺500 -MIPS的機器每秒能夠執行5億條指令,由於指令依靠的是電的性質,換句話說執行一次IO的時間能夠執行40萬條指令,數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間,顯然是個災難。下圖是計算機硬件延遲的對比圖,供你們參考:
考慮到磁盤IO是很是高昂的操做,計算機操做系統作了一些優化,當一次IO時,不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩衝區內,由於局部預讀性原理告訴咱們,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。每一次IO讀取的數據咱們稱之爲一頁(page)。具體一頁有多大數據跟操做系統有關,通常爲4k或8k,也就是咱們讀取一頁內的數據時候,實際上才發生了一次IO,這個理論對於索引的數據結構設計很是有幫助。
事實1 : 不一樣容量的存儲器,訪問速度差別懸殊。
最經常使用
的數據存儲在最快的存儲器中事實2 : 從磁盤中讀 1 B,與讀寫 1KB 的時間成本幾乎同樣
從以上數據中能夠總結出一個道理,索引查詢的數據主要受限於硬盤的I/O速度,查詢I/O次數越少,速度越快,因此B樹的結構才應需求而生;B樹的每一個節點的元素能夠視爲一次I/O讀取,樹的高度表示最多的I/O次數,在相同數量的總元素個數下,每一個節點的元素個數越多,高度越低,查詢所需的I/O次數越少;假設,一次硬盤一次I/O數據爲8K,索引用int(4字節)類型數據創建,理論上一個節點最多能夠爲2000個元素,2000*2000*2000=8000000000,80億條的數據只需3次I/O(理論值),可想而知,B樹作爲索引的查詢效率有多高;
另外也能夠看出一樣的總元素個數,查詢效率和樹的高度密切相關
B樹的高度
一棵含有N個總關鍵字數的m階的B樹的最大高度是多少?
log(m/2)(N+1)/2 + 1 ,log以(m/2)爲低,(N+1)/2的對數再加1
算法以下
B+樹是應文件系統所需而產生的B樹的變形樹,那麼可能必定會想到,既然有了B樹,又出一個B+樹,那B+樹必然是有不少優勢的
B+樹的特徵:
爲何說B+樹比B樹更適合數據庫索引?
1)B+樹的磁盤讀寫代價更低
B+樹的內部結點並無指向關鍵字具體信息的指針。所以其內部結點相對B 樹更小。若是把全部同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的須要查找的關鍵字也就越多。相對來講IO讀寫次數也就下降了;
2)B+樹查詢效率更加穩定
因爲非終結點並非最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。因此任何關鍵字的查找必須走一條從根結點到葉子結點的路。全部關鍵字查詢的路徑長度相同,致使每個數據的查詢效率至關;
3)B+樹便於範圍查詢(最重要的緣由,範圍查找是數據庫的常態)
B樹在提升了IO性能的同時並無解決元素遍歷的我效率低下的問題,正是爲了解決這個問題,B+樹應用而生。B+樹只須要去遍歷葉子節點就能夠實現整棵樹的遍歷。並且在數據庫中基於範圍的查詢是很是頻繁的,而B樹不支持這樣的操做或者說效率過低;不懂能夠看看這篇解讀-》範圍查找
補充:B樹的範圍查找用的是中序遍歷,而B+樹用的是在鏈表上遍歷;
B+樹以下: