MySQL索引原理——B樹

1、MyISAM是MySQL 5.5之前版本默認的存儲引擎,從5.5之後,InnoDB開始成爲MySQL默認的存儲引擎。MyISAM使用B-Tree實現主鍵索引、唯一索引和非主鍵索引。InnoDB中非主鍵索引使用的是B-Tree數據結構,而主鍵索引使用的是B+Tree。

2、InnoDB存儲引擎中有頁(Page)的概念,頁是其磁盤管理的最小單位。InnoDB存儲引擎中默認每個頁的大小爲16KB,可通過參數innodb_page_size將頁的大小設置爲4K、8K、16K,在MySQL中可通過如下命令查看頁的大小:

mysql> show variables like 'innodb_page_size';

而系統一個磁盤塊的存儲空間往往沒有這麼大,因此InnoDB每次申請磁盤空間時都會是若干地址連續磁盤塊來達到頁的大小16KB。InnoDB在把磁盤數據讀入到磁盤時會以頁爲基本單位,在查詢數據時如果一個頁中的每條數據都能有助於定位數據記錄的位置,這將會減少磁盤I/O次數,提高查詢效率。B-Tree結構的數據可以讓系統高效的找到數據所在的磁盤塊。

3、B-Tree定義一:

一棵m階的B-Tree,或者爲空樹,或者滿足下列特性: 

  • 樹中每個結點至多有m棵子樹; 
  • 若根結點不是葉子結點,則至少有兩棵子樹; 
  • 除根節點之外的所有非終端結點至少有[m/2]棵子樹; 
  • 所有非終端結點中包含下列信息數據: (n,A0,K1,A1,K2,A2……Kn,An) 。其中,n爲關鍵字的數目,K(i)爲關鍵字,且K(i) < K(i+1), Ai爲指向子樹根結點的指針,且指針A(i-1)所指子樹中所有結點的關鍵字均小於Ki,Ai所指子樹中所有結點的關鍵字均大於Ki; 
  • 所有葉子結點都出現在同一層次上;

B-Tree定義二:

爲了描述B-Tree,首先定義一條數據記錄爲一個二元組[key, data],key爲記錄的鍵值,對於不同數據記錄,key是互不相同的;data爲數據記錄除key外的數據。那麼B-Tree是滿足下列條件的數據結構:

  • d爲大於1的一個正整數,稱爲B-Tree的度。
  • h爲一個正整數,稱爲B-Tree的高度。
  • 每個非葉子節點由n-1個key和n個指針組成,其中d<=n<=2d。
  • 子節點最少包含一個key和兩個指針,最多包含2d-1個key和2d個指針,葉節點的指針均爲null。
  • 所有葉節點具有相同的深度,等於樹高h。
  • key和指針互相間隔,節點兩端是指針。
  • 一個節點中的key從左到右遞增排列。
  • 如果某個指針在節點node的左右相鄰key分別是key1和key2且不爲null,則其指向的節點的所有key小於key2且大於key1.

4、B+Tree

與B-Tree相比,B+Tree有以下不同點:

  • 每個節點的指針上限爲2d而不是2d+1。
  • 內節點不存儲data,只存儲key;葉子節點不存儲指針。
  • 非葉子結點的子樹指針與關鍵字個數相同;
  • 非葉子結點的子樹指針P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹(B-Tree左右都是開區間);
  • 爲所有葉子結點增加一個鏈指針;

5、數據庫中的B+Tree索引可以分爲聚集索引(clustered index)和輔助索引(secondary index)。

 

上面的B+Tree示例圖在數據庫中的實現即爲聚集索引,聚集索引的B+Tree中的葉子節點存放的是整張表的行記錄數據。輔助索引與聚集索引的區別在於輔助索引的葉子節點並不包含行記錄的全部數據,而是隻存儲相應行數據的輔助索引值和聚集索引鍵,即主鍵。當通過輔助索引來查詢數據時,InnoDB存儲引擎會遍歷輔助索引找到主鍵,然後再通過主鍵在聚集索引中找到完整的行記錄數據。

6、InnoDB索引實現:

InnoDB採用B+tree作爲索引結構,但具體實現方式卻與MyISAM不同。

1)主鍵索引:

MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引。

 

(圖inndb主鍵索引)是InnoDB主索引(同時也是數據文件)的示意圖,可以看到葉節點包含了完整的數據記錄。這種索引叫做聚集索引。因爲InnoDB的數據文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識數據記錄的列作爲主鍵,如果不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段作爲主鍵,這個字段長度爲6個字節,類型爲長整形。

2)InnoDB輔助索引

InnoDB的所有輔助索引都引用主鍵作爲data域。例如,下圖爲定義在Col3上的一個輔助索引:

 

InnoDB 表是基於聚簇索引建立的。因此InnoDB 的索引能提供一種非常快速的主鍵查找性能。不過,它的輔助索引(Secondary Index, 也就是非主鍵索引)也會包含主鍵列,所以,如果主鍵定義的比較大,其他索引也將很大。如果想在表上定義很多索引,則爭取儘量把主鍵定義得小一些,因爲InnoDB 不會壓縮索引。

聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然後用主鍵到主索引中檢索獲得記錄。

不同存儲引擎的索引實現方式對於正確使用和優化索引都非常有幫助,例如知道了InnoDB的索引實現後,就很容易明白爲什麼不建議使用過長的字段作爲主鍵,因爲所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。再例如,用非單調的字段作爲主鍵在InnoDB中不是個好主意,因爲InnoDB數據文件本身是一顆B+Tree,非單調的主鍵會造成在插入新記錄時數據文件爲了維持B+Tree的特性而頻繁地分裂調整,十分低效,而使用自增字段作爲主鍵則是一個很好的選擇。

 InnoDB索引和MyISAM索引的區別:

一是主索引的區別,InnoDB的數據文件本身就是索引文件。而MyISAM的索引和數據是分開的。

二是輔助索引的區別:InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。而MyISAM的輔助索引和主索引沒有多大區別。

7、B樹(B-樹/B+樹)插入操作:

插入一個元素時,首先查看在B樹中是否存在,如果不存在,即查找操作會在葉子結點處結束,然後在葉子結點中插入該新的元素,注意:如果葉子結點空間足夠,則需要向右移動該葉子結點中大於新插入關鍵字的元素(以保證節點的順序性),如果空間滿了以致沒有足夠的空間去添加新的元素,則將該結點進行“分裂”,將一半數量的關鍵字元素分裂到新的其相鄰右結點中,中間關鍵字元素上移到父結點中(當然,如果父結點空間滿了,也同樣需要“分裂”操作),而且當結點中關鍵元素向右移動了,相關的指針也需要向右移。如果在根結點插入新元素,空間滿了,則進行分裂操作,這樣原來的根結點中的中間關鍵字元素向上移動到新的根結點中,因此導致樹的高度增加一層。如下圖所示:

 

8、B樹(B-樹/B+樹)刪除操作

首先查找B樹中需刪除的元素,如果該元素在B樹中存在,則將該元素在其結點中進行刪除,如果刪除該元素後,首先判斷該元素是否有左右孩子結點,如果有,則上移孩子結點中的某相近元素(“左孩子最右邊的節點”或“右孩子最左邊的節點”)到父節點中;如果沒有,則直接刪除。刪除元素,移動相應元素之後(如果沒孩子節點則沒有移動),如果某結點中元素數目(即關鍵字數)小於ceil(m/2)-1,則需要看其某相鄰兄弟結點是否豐滿(結點中元素個數大於ceil(m/2)-1),如果豐滿,則向父節點借一個元素來滿足條件;如果其相鄰兄弟都剛脫貧,即借了之後其結點數目小於ceil(m/2)-1,則該結點與其相鄰的某一兄弟結點進行“合併”成一個結點,以此來滿足條件。

9、B*-tree

B*-tree是B+-tree的變體,在B+樹的基礎上(所有的葉子結點中包含了全部關鍵字的信息,及指向含有這些關鍵字記錄的指針),B*樹中非根和非葉子結點再增加指向兄弟的指針;B*樹定義了非葉子結點關鍵字個數至少爲(2/3)*M,即塊的最低使用率爲2/3(代替B+樹的1/2)。給出了一個簡單實例,如下圖所示:

 

B+樹的分裂:當一個結點滿時,分配一個新的結點,並將原結點中1/2的數據複製到新結點,最後在父結點中增加新結點的指針;B+樹的分裂隻影響原結點和父結點,而不會影響兄弟結點,所以它不需要指向兄弟的指針。

B*樹的分裂:當 如果它的下一個兄弟結點未滿,那麼將一部分數據移到兄弟結點中,再在原結點插入關鍵字,最後修改父結點中兄弟結點的關鍵字(因爲兄弟結點的關鍵字範圍改變了);如果兄弟也滿了,則在原結點與兄弟結點之間增加新結點,並各複製1/3的數據到新結點,最後在父結點增加新結點的指針。

所以,B*樹分配新結點的概率比B+樹要低,空間使用率更高。

10、總結:

通過以上介紹,大致將B樹,B+樹,B*樹總結如下:

B樹:有序數組+平衡多叉樹;

B+樹:有序數組鏈表+平衡多叉樹;

B*樹:一棵豐滿的B+樹。

相比B-tree,B+tree有個好處,那就是方便掃庫,B樹必須用中序遍歷的方法按序掃庫,而B+樹直接從葉子結點挨個掃一遍就完了,B+樹支持range-query非常方便,而B樹不支持。這是數據庫選用B+樹的最主要原因。

當然,B樹的好處,就是成功查詢特別有利,因爲樹的高度總體要比B+樹矮。不成功的情況下,B樹也比B+樹稍稍佔一點點便宜。

有很多基於頻率的搜索是選用B樹,越頻繁query的結點越往根上走,前提是需要對query做統計,而且要對key做一些變化。另外B樹也好B+樹也好,根或者上面幾層因爲被反覆query,所以這幾塊基本都在內存中,不會出現讀磁盤IO,一般已啓動的時候,就會主動換入內存。

mysql 底層存儲是用B+樹實現的,因爲MySQL的索引是存儲在磁盤上的。內存中B+樹是沒有優勢的,但是一到磁盤,B+樹的威力就出來了。

 

11、相關參考文檔:

http://blog.csdn.net/ifollowrivers/article/details/73614549 MySQL中B+Tree索引原理

http://blog.csdn.net/define_danmu_primer/article/details/70757784 MySQL索引使用的數據結構:B-Tree和B+Tree

http://blog.csdn.net/u013400245/article/details/52824744 B樹B+樹的原理和操作

https://www.cnblogs.com/gym333/p/6877023.html 【數據結構】B-Tree, B+Tree, B*樹介紹 轉

http://blog.csdn.net/endlu/article/details/51720299 BTree和B+Tree詳解

http://blog.csdn.net/u013400245/article/details/52824744 B樹B+樹的原理和操作

https://www.jianshu.com/p/17a7b3d321d8 B-樹和B+樹

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章