MySQL索引的數據結構-B+樹介紹

1、樹

樹狀圖是一種數據結構 ,它是由n(n>=1)個有限結點組成一個具備層次關係的集合。把它叫作「樹」是由於它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。
它具備如下的特色:每一個結點有零個或多個子結點;沒有父結點的結點稱爲根結點;每個非根結點有且只有一個父結點;除了根結點外,每一個子結點能夠分爲多個不相交的子樹算法

205-MySQL索引的數據結構-B樹介紹-01.png?x-oss-process=style/watermark

根結點:Asql

父節點:A是B,C的父節點數據庫

葉子節點:D,E是葉子節點數據結構

樹的深度/樹的高度:高度爲3測試

2、B+樹

前面講了索引的基本原理,數據庫的複雜性,又講了操做系統的相關知識,目的就是讓你們瞭解,任何一種數據結構都不是憑空產生的,必定會有它的背景和使用場景,咱們如今總結一下,咱們須要這種數據結構可以作些什麼,其實很簡單,那就是:每次查找數據時把磁盤IO次數控制在一個很小的數量級,最好是常數數量級。那麼咱們就想到若是一個高度可控的多路搜索樹是否能知足需求呢?就這樣,b+樹應運而生(B+樹是經過二叉查找樹,再由平衡二叉樹,B樹演化而來)。優化

205-MySQL索引的數據結構-B樹介紹-02.png?x-oss-process=style/watermark

2.1 B+樹性質

  1. 索引字段要儘可能的小:經過上面的分析,咱們知道IO次數取決於b+數的高度h,假設當前數據表的數據爲N,每一個磁盤塊的數據項的數量是m,則有h=㏒(m+1)N,當數據量N必定的狀況下,m越大,h越小;而m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個數據頁的大小,是固定的,若是數據項佔的空間越小,數據項的數量越多,樹的高度越低。這就是爲何每一個數據項,即索引字段要儘可能的小,好比int佔4字節,要比bigint8字節少一半。這也是爲何b+樹要求把真實的數據放到葉子節點而不是內層節點,一旦放到內層節點,磁盤塊的數據項會大幅度降低,致使樹增高。當數據項等於1時將會退化成線性表。
  2. 索引的最左匹配特性:當b+樹的數據項是複合的數據結構,好比(name,age,sex)的時候,b+數是按照從左到右的順序來創建搜索樹的,好比當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來肯定下一步的所搜方向,若是name相同再依次比較age和sex,最後獲得檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪一個節點,由於創建搜索樹的時候name就是第一個比較因子,必需要先根據name來搜索才能知道下一步去哪裏查詢。好比當(張三,F)這樣的數據來檢索時,b+樹能夠用name來指定搜索方向,但下一個字段age的缺失,因此只能把名字等於張三的數據都找到,而後再匹配性別是F的數據了, 這個是很是重要的性質,即索引的最左匹配特性。

3、彙集索引和輔助索引

在數據庫中,B+樹的高度通常都在2~4層,這也就是說查找某一個鍵值的行記錄時最多隻須要2到4次IO,這倒不錯。由於當前通常的機械硬盤每秒至少能夠作100次IO,2~4次的IO意味着查詢時間只須要0.02~0.04秒。spa

數據庫中的B+樹索引能夠分爲彙集索引(clustered index)和輔助索引(secondary index),操作系統

彙集索引與輔助索引相同的是:無論是彙集索引仍是輔助索引,其內部都是B+樹的形式,即高度是平衡的,葉子結點存放着全部的數據。設計

彙集索引與輔助索引不一樣的是:葉子結點存放的是不是一整行的信息

3.1 彙集索引

InnoDB存儲引擎表是索引組織表,即表中數據按照主鍵順序存放。
而彙集索引(clustered index)就是按照每張表的主鍵構造一棵B+樹,同時葉子結點存放的即爲整張表的行記錄數據,也將彙集索引的葉子結點稱爲數據頁。
彙集索引的這個特性決定了索引組織表中數據也是索引的一部分。同B+樹數據結構同樣,每一個數據頁都經過一個雙向鏈表來進行連接。

若是未定義主鍵,MySQL取第一個惟一索引(unique)並且只含非空列(NOT NULL)做爲主鍵,InnoDB使用它做爲聚簇索引。

若是沒有這樣的列,InnoDB就本身產生一個這樣的ID值,它有六個字節,並且是隱藏的,使其做爲聚簇索引。

因爲實際的數據頁只能按照一棵B+樹進行排序,所以每張表只能擁有一個彙集索引。
在多數狀況下,查詢優化器傾向於採用彙集索引。由於彙集索引可以在B+樹索引的葉子節點上直接找到數據。
此外因爲定義了數據的邏輯順序,彙集索引可以特別快地訪問針對範圍值得查詢。

205-MySQL索引的數據結構-B樹介紹-13.png?x-oss-process=style/watermark

彙集索引的好處之一:它對主鍵的排序查找和範圍查找速度很是快,葉子節點的數據就是用戶所要查詢的數據。如用戶須要查找一張表,查詢最後的10位用戶信息,因爲B+樹索引是雙向鏈表,因此用戶能夠快速找到最後一個數據頁,並取出10條記錄

# 參照第六小結測試索引的準備階段來建立出表s1
mysql> desc s1; #最開始沒有主鍵
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(6)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
rows in set (0.00 sec)

mysql> explain select * from s1 order by id desc limit 10; #Using filesort,須要二次排序
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra          |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
|  1 | SIMPLE      | s1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 2633472 |   100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
row in set, 1 warning (0.11 sec)

mysql> alter table s1 add primary key(id); #添加主鍵
Query OK, 0 rows affected (13.37 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from s1 order by id desc limit 10; #基於主鍵的彙集索引在建立完畢後就已經完成了排序,無需二次排序
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
|  1 | SIMPLE      | s1    | NULL       | index | NULL          | PRIMARY | 4       | NULL |   10 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
row in set, 1 warning (0.04 sec)

彙集索引的好處之二:範圍查詢(range query),即若是要查找主鍵某一範圍內的數據,經過葉子節點的上層中間節點就能夠獲得頁的範圍,以後直接讀取數據頁便可

mysql> alter table s1 drop primary key;
Query OK, 2699998 rows affected (24.23 sec)
Records: 2699998  Duplicates: 0  Warnings: 0

mysql> desc s1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(6)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
rows in set (0.12 sec)

mysql> explain select * from s1 where id > 1 and id < 1000000; # 沒有彙集索引,預估須要檢索的rows數以下
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
|  1 | SIMPLE      | s1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 2690100 |    11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
row in set, 1 warning (0.00 sec)

mysql> alter table s1 add primary key(id);
Query OK, 0 rows affected (16.25 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from s1 where id > 1 and id < 1000000; # 有彙集索引,預估須要檢索的rows數以下
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
|  1 | SIMPLE      | s1    | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL | 1343355 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
row in set, 1 warning (0.09 sec)

3.2 輔助索引

表中除了彙集索引外其餘索引都是輔助索引(Secondary Index,也稱爲非彙集索引),與彙集索引的區別是:輔助索引的葉子節點不包含行記錄的所有數據。

葉子節點除了包含鍵值之外,每一個葉子節點中的索引行中還包含一個書籤(bookmark)。該書籤用來告訴InnoDB存儲引擎去哪裏能夠找到與索引相對應的行數據。

因爲InnoDB存儲引擎是索引組織表,所以InnoDB存儲引擎的輔助索引的書籤就是相應行數據的彙集索引鍵。以下圖

205-MySQL索引的數據結構-B樹介紹-14.png?x-oss-process=style/watermark

輔助索引的存在並不影響數據在彙集索引中的組織,所以每張表上能夠有多個輔助索引,但只能有一個彙集索引。當經過輔助索引來尋找數據時,InnoDB存儲引擎會遍歷輔助索引並經過葉子級別的指針得到只想主鍵索引的主鍵,而後再經過主鍵索引來找到一個完整的行記錄。

舉例來講,若是在一棵高度爲3的輔助索引樹種查找數據,那須要對這個輔助索引樹遍歷3次找到指定主鍵,若是彙集索引樹的高度一樣爲3,那麼還須要對彙集索引樹進行3次查找,最終找到一個完整的行數據所在的頁,所以一共須要6次邏輯IO訪問才能獲得最終的一個數據頁。

205-MySQL索引的數據結構-B樹介紹-15.png?x-oss-process=style/watermark

3.3 彙集索引和非彙集索引的區別

彙集索引

  1. 紀錄的索引順序與無力順序相同
    所以更適合between and和order by操做
  2. 葉子結點直接對應數據
    從中間級的索引頁的索引行直接對應數據頁
  3. 每張表只能建立一個彙集索引

非彙集索引

  1. 索引順序和物理順序無關
  2. 葉子結點不直接指向數據頁
  3. 每張表能夠有多個非彙集索引,須要更多磁盤和內容
  4. 多個索引會影響insert和update的速度

4、再看B+樹

B+樹和二叉樹、平衡二叉樹同樣,都是經典的數據結構。B+樹由B樹和索引順序訪問方法(ISAM,是否是很熟悉?對,這也是MyISAM引擎最初參考的數據結構)演化而來,可是在實際使用過程當中幾乎已經沒有使用B樹的狀況了。

B+樹的定義十分複雜,所以只簡要地介紹B+樹:B+樹是爲磁盤或其餘直接存取輔助設備而設計的一種平衡查找樹,在B+樹中,全部記錄節點都是按鍵值的大小順序存放在同一層的葉節點中,各葉節點指針進行鏈接。

咱們先來看一個B+樹,其高度爲2,每頁可存放4條記錄,扇出(fan out)爲5。

205-MySQL索引的數據結構-B樹介紹-03.png?x-oss-process=style/watermark

能夠看出,全部記錄都在葉節點中,而且是順序存放的,若是咱們從最左邊的葉節點開始順序遍歷,能夠獲得全部鍵值的順序排序:五、十、1五、20、2五、30、50、5五、60、6五、7五、80、8五、90。

4.1 B+樹的插入操做

B+樹的插入必須保證插入後葉節點中的記錄依然排序,同時須要考慮插入B+樹的三種狀況,每種狀況均可能會致使不一樣的插入算法,如表5-1所示。

205-MySQL索引的數據結構-B樹介紹-04.png?x-oss-process=style/watermark

咱們用實例來分析B+樹的插入,咱們插入28這個鍵值,發現當前Leaf Page和Index Page都沒有滿,咱們直接插入就能夠了。

205-MySQL索引的數據結構-B樹介紹-05.png?x-oss-process=style/watermark

此次咱們再插入一條70這個鍵值,這時原先的Leaf Page已經滿了,可是Index Page尚未滿,符合表5-1的第二種狀況,這時插入Leaf Page後的狀況爲50、5五、60、6五、70。咱們根據中間的值60拆分葉節點。

205-MySQL索引的數據結構-B樹介紹-06.png?x-oss-process=style/watermark

由於圖片顯示的關係,此次我沒有能在各葉節點加上雙向鏈表指針。最後咱們來插入記錄95,這時符合表5-1討論的第三種狀況,即Leaf Page和Index Page都滿了,這時須要作兩次拆分。

205-MySQL索引的數據結構-B樹介紹-07.png?x-oss-process=style/watermark

能夠看到,無論怎麼變化,B+樹老是會保持平衡。可是爲了保持平衡,對於新插入的鍵值可能須要作大量的拆分頁(split)操做,而B+樹主要用於磁盤,所以頁的拆分意味着磁盤的操做,應該在可能的狀況下儘可能減小頁的拆分。所以,B+樹提供了旋轉(rotation)的功能。

旋轉發生在Leaf Page已經滿了、可是其左右兄弟節點沒有滿的狀況下。(旋轉是爲了減小拆分頁,若是葉子節點的左右兄弟節點還有空位置,那就旋轉一下就能把當前數據插入了;若是本身和左右兄弟位置都滿了那再怎麼旋轉也出不來位置了,只能拆分頁了)這時B+樹並不會急於去作拆分頁的操做,而是將記錄移到所在頁的兄弟節點上。一般狀況下,左兄弟被首先檢查用來作旋轉操做,這時咱們插入鍵值70,其實B+樹並不會急於去拆分葉節點,而是作旋轉,50,55,55旋轉。

205-MySQL索引的數據結構-B樹介紹-08.png?x-oss-process=style/watermark

能夠看到,採用旋轉操做使B+樹減小了一次頁的拆分操做,而這時B+樹的高度依然仍是2。

4.2 B+樹的刪除操做

B+樹使用填充因子(fill factor)來控制樹的刪除變化,50%是填充因子可設的最小值。B+樹的刪除操做一樣必須保證刪除後葉節點中的記錄依然排序,同插入同樣,B+樹的刪除操做一樣須要考慮如表5-2所示的三種狀況,與插入不一樣的是,刪除根據填充因子的變化來衡量。

205-MySQL索引的數據結構-B樹介紹-09.png?x-oss-process=style/watermark

首先,刪除鍵值爲70的這條記錄,該記錄符合表5-2討論的第一種狀況,刪除後。

205-MySQL索引的數據結構-B樹介紹-10.png?x-oss-process=style/watermark

接着咱們刪除鍵值爲25的記錄,這也是表5-2討論的第一種狀況,可是該值仍是Index Page中的值,所以在刪除Leaf Page中25的值後,還應將25的右兄弟節點的28更新到Page Index中,最後可獲得圖。

205-MySQL索引的數據結構-B樹介紹-11.png?x-oss-process=style/watermark

最後咱們來看刪除鍵值爲60的狀況,刪除Leaf Page中鍵值爲60的記錄後,填充因子小於50%,這時須要作合併操做,一樣,在刪除Index Page中相關記錄後須要作Index Page的合併操做,最後獲得圖。

205-MySQL索引的數據結構-B樹介紹-12.png?x-oss-process=style/watermark

相關文章
相關標籤/搜索