第15期:索引設計(索引組織方式 B+ 樹)

image

談到索引,你們並不陌生。索引自己是一種數據結構,存在的目的主要是爲了縮短數據檢索的時間,最大程度減小磁盤 IO。數據庫

任何有數據的場景幾乎都有索引,好比手機通信錄、文件系統(ext4xfsntfs)、數據庫系統(MySQLOracle)。數據庫系統和文件系統通常都採用 B+ 樹來存儲索引信息,B+ 樹兼顧寫和讀的性能,最極端時檢索複雜度爲 O(logN),其中 N 指的是節點數量,logN 表示對磁盤 IO 掃描的總次數。數據結構

MySQL 支持的索引結構有四種:B+ 樹,R 樹,HASH,FULLTEXT。性能

本篇簡單介紹下 B+ 樹,下一篇講 MySQL 經常使用的兩種引擎 MyISAM 和 InnoDB 的 B+ 樹索引實現,其他的後面會講到。優化

1、什麼是二叉樹?

再講什麼是 B+ 樹以前,先來了看下什麼是二叉樹。spa

樹自己是一種數據存儲結構,由於相似現實生活中的樹而命名。3d

一個看似沒有修剪過的樹,其實這是一棵二叉樹,每一個節點最多有兩個子節點指針

image

樹相關的基礎概念:blog

拿圖 1 這棵樹舉例說明:排序

  • 根節點:6 爲根節點,根節點沒有父節點,有兒子節點,通常叫作 ROOT 節點;
  • 兒子節點:8 和 4 是 6 的兒子節點,4 是左兒子,8 是右兒子;
  • 父節點:6 是 4 和 8 的父節點,父節點是兒子節點的上層節點;
  • 葉子節點:4 和 5 是葉子節點,葉子節點指的是除根節點外沒有兒子的節點;
  • 兄弟節點:8 和 4 互爲兄弟節點,由於有共同的父親 6。10,9,7 三個節點沒有兄弟,都只有一個兒子;
  • 層數:一棵樹的節點層數。圖 1 層數爲 6;
  • 高度:自下向上遍歷,從葉子節點遍歷到根節點所須要的節點數量。葉子節點 5 到根節點遍歷 7,9,10,8,6,這棵樹的高度爲 5;
  • 深度:自上而下遍歷,從根節點到葉子節點遍歷所須要的節點數量,一樣,這棵樹的深度也是 5;
  • 高度和深度通常以 0 開始計算,固然也有按照從 1 開始計算的;
  • 平衡因子:某節點的左子樹與右子樹深度的差值,通常結果爲絕對值。索引

    • 若是任何一個子樹不存在,按照 0 處理。好比節點 10 的平衡因子就是 3;

圖 1 是一顆很是普通的樹,很是容易退化爲一張鏈表。若是把圖 1 換成以下圖, 根節點就變爲 4,6 退化爲 4 的兒子節點,這棵樹就退化爲一張鏈表。

image

鏈表的查找很是慢,只能按照節點順序查找,每一個節點都遍歷一遍,時間複雜度爲 O(n),沒法隨機查找。

2、平衡二叉樹(AVL)

那對圖 1 進行下改造,把數據從新節點從新鏈接下,圖 2 以下:

image

圖 2 能夠看到如下特性:

1. 全部左子樹的節點都小於其對應的父節點(4,5,6)<(7);(4)<(5);(8)< (9);

2. 全部右子樹上的節點都大於其對應的父節點(8,9,10)>(7);(6)>(5);(10)>(9);

3. 每一個節點的平衡因子差值絕對值 <=1;

4. 每一個節點都符合以上三個特徵。

知足這樣條件的樹叫平衡二叉樹(AVL)樹。

問:那再次查找節點 5,須要遍歷多少次呢?

因爲數據是按照順序組織的,那查找起來很是快,從上往下找:7-5,只須要在左子樹上查找,也就是遍歷 2 次就找到了 5。假設要找到葉子節點 10,只須要在右子樹上查找,那也最多須要 3 次,7-9-10。也就說 AVL 樹在查找方面性能很好,最壞的狀況是找到一個節點須要消耗的次數也就是樹的層數, 複雜度爲 O(logN)

若是節點很是多呢?假設如今有 31 個節點,用 AVL 樹表示如圖 3:

image

圖 3 是一棵高度爲 4 的 AVL 樹,有 5 層共 31 個節點,橙色是 ROOT 節點,藍色是葉子節點。對 AVL 樹的查找來看起來已經很完美了,能不能再優化下?好比,可否把這個節點裏存放的 KEY 增長?可否減小樹的總層數?那減小縱深只能從橫向來想辦法,這時候能夠考慮用多叉樹。

3、B 樹

B 樹是一種多叉的 AVL 樹。B-Tree 減小了 AVL 數的高度,增長了每一個節點的 KEY 數量。

B 樹的特性:(m 爲階數:結點的孩子個數最大值)

1. 樹中每一個節點最多含有 m 個孩子節點 (m>=2);

2. 除根節點和葉子結點外,其餘節點的孩子數量 >=ceil(m / 2);

3. 若根節點不是葉子結點,最少有兩個孩子

  • 特殊狀況:沒有孩子的根結點,即根結點爲葉子結點,整棵樹只有一個根節點; 

4. 每一個非葉子結點中包含有 n 個關鍵字信息:(n,P0,K1,P1,K2,P2,......,Kn,Pn) 其中:

  • Ki (i=1...n) 爲關鍵字,且關鍵字按順序升序排序 K(i-1)< Ki
  • Pi 爲指向兒子節點的指針,且指針 P(i-1) 指向的兒子節點裏全部關鍵字均小於 Ki,但都大於 K(i-1)
  • 關鍵字的個數 n 必須知足:[ceil(m / 2)-1]<= n <= m-1
  • 若是一個結點有 n 個關鍵字,那麼該結點有 n+1 個分支。這 n+1 個關鍵字按照遞增順序排列
  • 全部葉子結點都出如今同一層,是全部遍歷的終點位置

按照這個要求,把圖 3 簡單變爲一棵 B 樹,見圖 4:

image

圖 4 是一棵 4 階 B 樹,總共有 11 個節點,節點數比圖 3 少了 20 個;層數爲 3,比圖 3 少了兩層。實際應用中,每一個最小單元不是 KEY,而通常是按照塊(BLOCK)來算。好比磁盤文件系統 EXT4 每塊 4KB;數據庫好比 PostgreSQL 是 8KB,MySQL InnoDB 是 16KB, MySQL NDB  是 32KB 等。

因此再次理清圖 4 的 B 樹,變爲圖 5:

image

圖 5 每一個節點的基本單元是一個磁盤塊(BLOCK,默認 4KB),根節點含有一個鍵值,其餘節點含有 3 個鍵值,每一個磁盤塊包含對應的鍵值與數據。

好比如今要讀取 KEY 爲 31 的記錄:先找到根節點磁盤塊(1),讀入內存。(第一次 IO);關鍵字 31 大於區間(16,),根據指針 P2 找到磁盤塊 3,讀入內存(第二次 IO);31 大於區間(20,24,28),根據指針 P4 讀取磁盤塊 11(第三次 IO),在磁盤塊 11 中找到 KEY 爲 31 的記錄,返回結果。這期間有三次磁盤 IO 的讀取。能夠明確看到,B 樹相對於 AVL 樹,減小了樹的節點數與樹的深度,減小了磁盤 IO。

看到這裏其實有一個問題,三次 IO,前兩次 IO 其實從磁盤讀取了沒必要要的數據,由於只用比較 KEY,因此非葉子節點對應的 DATA 徹底沒有必要,若是 DATA 很大,那徹底是浪費內存資源。考慮下可否把非葉子節點的 DATA 拿掉?

4、B+ 樹

B+ 樹是對 B 樹的一個小升級。大部分數據庫的索引都是基於 B+ 樹存儲的。MySQL 的 MyISAM 和 InnoDB 引擎的索引都是基於 B+ 樹存儲。

B+ 樹最大的幾個特色:

1. 非葉子節點只保留 KEY,放棄 DATA;

2. KEY 和 DATA一塊兒,在葉子節點,而且保存爲一個有序鏈表(正序,反序,或者雙向);

3. B+ 樹的查找與 B 樹不一樣,當某個結點的 KEY 與所查的 KEY 相等時,並不中止查找,而是沿着這個 KEY 左邊的指針向下,一直查到該關鍵字所在的葉子結點爲止。

那對圖 5 的 B 樹作一個調整,變爲如下 B+ 樹,見圖 6:

image

圖 6 是一棵 6 階 B+ 樹。不一樣於圖 5,非葉子節點再也不包含除了主鍵外的數據,數據所有放在葉子節點,而且全部葉子節點存放在一個單向鏈表裏,固然也能夠雙向鏈表。能夠看到,B+ 樹同時具備平衡多叉樹和鏈表的優勢,便可兼顧 B 樹對範圍查找的高效,又可兼顧鏈表隨機寫入的高效, 這也是大部分數據庫都用 B+ 樹來存儲索引的緣由。

本篇是爲了下一篇介紹 MySQL 的兩種經常使用引擎:MyISAM 和 InnoDB 索引結構作了一個鋪墊,下期見。


關於 MySQL 的技術內容,大家還有什麼想知道的嗎?趕忙留言告訴小編吧!

image

相關文章
相關標籤/搜索