數據庫索引爲何要使用樹結構存儲呢?
這個還不簡單,樹的查詢效率高,並且能夠保持有序。
既然這樣,爲何索引沒有使用二叉查找樹來實現呢?
這就不明白了,明明二叉查找樹時間複雜度是O(logN),性能已經足夠高了,難道B樹能夠比它更快? html
其實從算法邏輯來說,二叉樹查找樹的查找速度和比較次數都是最小的,可是咱們不得不考慮一個現實問題:磁盤IO ;數據索引是存儲在磁盤上的,當數據量比較大的時候,索引的大小可能有幾個G甚至更多。
當咱們利用索引查詢的時候,能把整個索引所有加載到內存嗎?顯然不可能。能作的只有逐一加載每一個磁盤頁,這裏的磁盤頁對應索引樹的節點。
在二叉查找樹裏,磁盤的IO次數等於索引樹的高度。 mysql
既然如此,爲了減小磁盤的IO次數,咱們就須要把本來「瘦高」的樹結構變成「矮胖」,這是B-樹的特徵之一。
B-樹是一種多路平衡查找樹,它的每個節點最多包含k個孩子,k被稱爲B樹的階。
k的大小取決於磁盤頁的大小。 算法
下面來具體介紹如下B-樹(Balance Tree),一個m階的B樹具備以下幾個特徵:sql
下面以3階的B-樹爲例,來看看B-樹的具體結構。數據庫
【 9 】 / \ / \ 【2 6】 【12】 / | \ / \ / | \ / \ / | \ / \ 1 【3 5】 8 11 【13 15】
這棵樹中,重點看【2 6】節點,該節點有兩個元素2和6,又有三個孩子1,【3 5】和8。
其中1小於元素2,6之間,8大於【3 5】,正好符合剛剛所列的幾條特徵。 性能
演示下B-樹的查詢過程,假如咱們要查詢數值爲5的節點。
第一次IO,查到 9,比較9
第二次IO,查到 【2 6】 比較2,比較6
第三次IO,查到 【3 5】 比較3, 比較5優化
雖然 比較次數 並不比二叉查找樹少,尤爲當單一節點中的元素數量不少時。
可是相比磁盤IO的速度,內存中 比較的操做 耗時幾乎能夠忽略,因此只要樹的高度足夠低,IO次數足夠少,就能夠提升查找性能。
相比之下節點內部元素多一些並不要緊,最多就是幾回內存交換操做而已,只要不超過磁盤頁的大小,這就是B-樹的優點之一。 操作系統
但B-樹,插入新節點的過程就比較複雜,並且分紅不少種狀況。因此咱們舉個典型的例子,上圖中插入4.
自頂下查找4的節點位置,發現4應該插入【3 5】之間
節點【3 5】已是兩個元素節點,根據特徵2要求,沒法再增長了。父節點【2 6】也是兩元素節點,也沒法再增長,根節點9是單元素節點,能夠升級爲兩元素節點。因而拆分節點【3 5】和節點【2 6】,讓根節點升級爲兩元素節點【4 9】,節點6獨立成爲根節點的第二個孩子。指針
【 4 9 】 / | \ / | \ 2 6 【12】 / | / \ / \ / | / \ / \ / | / \ / \ 1 3 5 8 11 【13 15】
雖然維護樹結構麻煩,但也正由於如此,讓B-樹可以始終維持多路平衡。這就是B-樹的一大優點:自平衡。 code
下面在舉例說說B-樹的刪除,繼續上一顆樹,咱們要刪除元素11
自頂向下查找元素11的節點位置。
刪除11後,節點12只有一個孩子,不符合特徵1和5 (很差意思,我猜的,原文就直說不符合B樹規範)。
所以找出12,13,15這個節點的中位數,取代節點12,而節點12自身下已成爲第一個孩子。(此過程爲左旋)
【 4 9 】 / | \ / | \ 2 6 13 / | / \ / \ / | / \ / \ / | / \ / \ 1 3 5 8 12 15
雖然B-樹的插入和刪除,很複雜,沒看懂也不要緊,關鍵是理解B-樹的核心思想:B-樹主要應用於文件系統以及部分數據庫索引,好比著名的非關係型數據庫MongoDB。
不過大部分關係型數據庫,好比MySql,則使用B+樹做爲索引。
不少文檔裏,有時寫B-樹,有些寫B樹,但都是指balance tree,而不是balance binary tree
B+樹是基於B-樹的一種變體,有着比B-樹更高的查詢性能。
一個m階的B+樹具備以下幾個特徵:
【 8 15 】 / \ / \ 【2 5 8】 【11 15】 / | \ / \ / | \ / \ / | \ / \ 【1 2】->【3 5】->【6 8】->【9 11】--->【13 15】
在上面棵B+樹中,根節點元素8是子節點【2 5 8】的最大元素,也是葉子節點【6 8】的最大元素
根節點元素15也是子節點【11 15】的最大元素,也是葉子節點的【13 15】的最大元素
須要注意的是,根節點的最大元素(這裏是15),也就等同於整個B+樹的最大元素。之後不管插入刪除多少元素,始終要保持最大元素的根節點當中。
至於葉子節點,因爲父節點的元素出如今子節點,所以全部葉子節點包含了全量元素信息。
而且每個葉子節點都帶有指向下一個節點的指針,造成了一個有序鏈表。
B+樹還有一個特色,這個特色是在索引以外,確實是相當重要的特色,那就是【衛星數據】的位置。
所謂衛星數據,指的是索引元素所指向的數據記錄,好比數據庫中的某一行。在B-樹中,不管中間節點仍是葉子節點都帶有衛星數據。
而在B+樹中,只有葉子節點帶有衛星數據,其他中間節點僅僅是索引,沒有任何數據關聯。
須要補充的是,在數據庫的彙集索引(Clustered Index)中,葉子節點直接包含衛星數據。在非彙集索引(NonClustered Index)中,葉子節點帶有指向衛星數據的指針。
B+樹的好處主要體如今查詢性能上:
綜合起來,B+樹相比B-樹的優點有三個:
咱們能夠來算一筆帳,以InnoDB存儲引擎中默認每一個頁的大小爲16KB來計算,假設以int型的ID做爲索引關鍵字,那麼 一個int佔用4byte,由上圖能夠知道還有其餘的除主鍵之外的數據,姑且頁當成4byte,那麼這裏就是8byte,那麼16KB=161024byte,那麼咱們在這種場景下,能夠定義這個B-Tree的階樹爲 (161024)/8=2048.那麼這個樹將會有2048-1路,也就是原來平衡二叉樹(兩路)的1024倍左右,從而大大提升了查找效率與下降IO讀寫次數。
參考:https://www.cnblogs.com/wuzhe...
其實mysql數據頁結構不是單純16KB都給數據用,請參考https://www.cnblogs.com/bdsir...
看來不管是這裏的頁,仍是操做系統內存的頁page的概念,都是相似c語言的structure結構體的概念。
在4階的B+樹中(爲了圖好看直觀,*表明是頁面的可用空間)
【1 3 * *】 / \ / \ / \ 【1 2 * *】->【3 4 5 6】
插入記錄7時,因爲葉節點的頁面(下文簡稱葉頁面)中只能存放4條記錄,插入記錄7時,會致使葉頁面分裂,產生一個新的葉頁面。
【1 3 5 *】 / | \ / | \ / | \ / | \ 【1 2 * *】->【3 4 * *】->【5 6 7 *】
傳統B+樹頁面裂變操做及分析:
50%分裂策略的優點:
50%分裂策略的劣勢:
因爲傳統50%分裂的策略有不足之處。所以,針對B+樹索引的遞增/遞減插入進行了優化(目前全部的關係型數據庫,包括Oracle/InnoDB/PostgreSQL)。通過優化,上述B+樹索引,在記錄6插入完畢,記錄7插入引發分裂以後,新的B+樹結構以下:
【1 3 5 *】 / | \ / | \ / | \ / | \ 【1 2 * *】->【3 4 5 6】->【7 * * *】
對比上下兩個插入記錄7以後,B+樹索引的結構圖,能夠發現兩者有不少不一樣之處:
優化分裂策略的優點:
優化分裂策略的優點:
所以,此分裂的優化策略,僅僅是針對遞增遞減插入有效,針對隨機插入,就是去了優化的意義,反而帶來更高的分裂機率。 在InnoDB的實現中,爲每一個索引頁面維護了一個上次插入的位置,以及上次的插入是遞增/遞減的標識。根據這些信息,InnoDB可以判斷出新插入的頁面中的記錄,是否仍舊知足遞增/遞減的約束,若知足約束,則採用優化後的分裂策略;若不知足越蘇,則退回到50%的分類策略。這裏提下,遞增和遞減,指的是趨勢,因此Snowflake算法生成的序列是知足遞增的約束。