二叉搜索樹是最爲你們所熟知的一種數據結構,就不展開介紹了,它爲何不適合用做數據庫索引?mysql
(1)當數據量大的時候,樹的高度會比較高,數據量大的時候,查詢會比較慢;面試
(2)每一個節點只存儲一個記錄,可能致使一次查詢有不少次磁盤IO;sql
IO次數就是樹的高度,而「矮胖」就是b樹的特徵之一, B樹屬於多叉樹又名平衡多路查找樹(查找路徑不僅兩個)數據庫
B樹,如上圖,它的特色是:segmentfault
(1)再也不是二叉搜索,而是m叉搜索;數據結構
(2)葉子節點,非葉子節點,都存儲數據;機器學習
(3)中序遍歷,能夠得到全部節點;學習
畫外音,實在不想介紹這個特性:非根節點包含的關鍵字個數j知足,(┌m/2┐)-1 <= j <= m-1,節點分裂時要知足這個條件。大數據
B樹被做爲實現索引的數據結構被創造出來,是由於它可以完美的利用「局部性原理」。優化
什麼是局部性原理?
局部性原理的邏輯是這樣的:
(1)內存讀寫塊,磁盤讀寫慢,並且慢不少;
(2)磁盤預讀:磁盤讀寫並非按需讀取,而是按頁預讀,一次會讀一頁的數據,每次加載更多的數據,若是將來要讀取的數據就在這一頁中,能夠避免將來的磁盤IO,提升效率;
畫外音:一般,一頁數據是4K。
(3)局部性原理:軟件設計要儘可能遵循「數據讀取集中」與「使用到一個數據,大機率會使用其附近的數據」,這樣磁盤預讀能充分提升磁盤IO;
B樹爲什麼適合作索引?
(1)因爲是m分叉的,高度可以大大下降;
(2)每一個節點能夠存儲j個記錄,若是將節點大小設置爲頁大小,例如4K,可以充分的利用預讀的特性,極大減小磁盤IO;
總結:從平衡二叉樹、B樹、B+樹、B*樹整體來看它們的貫徹的思想是相同的,都是採用二分法和數據平衡策略來提高查找數據的速度。
B+樹,如上圖,還是m叉搜索樹,在B樹的基礎上,作了一些改進:
(1)非葉子節點再也不存儲數據,數據只存儲在同一層的葉子節點上;
畫外音:B+樹中根到每個節點的路徑長度同樣,而B樹不是這樣。
(2)葉子之間,增長了鏈表,獲取全部節點,再也不須要中序遍歷;
這些改進讓B+樹比B樹有更優的特性:
(1)範圍查找,定位min與max以後,中間葉子節點,就是結果集,不用中序回溯;
畫外音:範圍查詢在SQL中用得不少,這是B+樹比B樹最大的優點。
(2)葉子節點存儲實際記錄行,記錄行相對比較緊密的存儲,適合大數據量磁盤存儲;非葉子節點存儲記錄的PK,用於查詢加速,適合內存存儲;
(3)非葉子節點,不存儲實際記錄,而只存儲記錄的KEY的話,那麼在相同內存的狀況下,B+樹可以存儲更多索引;
最後,量化說下,爲何m叉的B+樹比二叉搜索樹的高度大大大大下降?
大概計算一下:
(1)局部性原理,將一個節點的大小設爲一頁,一頁4K,假設一個KEY有8字節,一個節點能夠存儲500個KEY,即j=500
(2)m叉樹,大概m/2<= j <=m,便可以差很少是1000叉樹
(3)那麼:
一層樹:1個節點,1*500個KEY,大小4K
二層樹:1000個節點,1000*500=50W個KEY,大小1000*4K=4M
三層樹:1000*1000個節點,1000*1000*500=5億個KEY,大小1000*1000*4K=4G
畫外音:額,幫忙看下有沒有算錯。
能夠看到,存儲大量的數據(5億),並不須要過高樹的深度(高度3),索引也不是太佔內存(4G)。
總結
數據庫索引用於加速查詢
雖然哈希索引是O(1),樹索引是O(log(n)),但SQL有不少「有序」需求,故數據庫使用樹型索引
InnoDB不支持哈希索引
數據預讀的思路是:磁盤讀寫並非按需讀取,而是按頁預讀,一次會讀一頁的數據,每次加載更多的數據,以便將來減小磁盤IO
局部性原理:軟件設計要儘可能遵循「數據讀取集中」與「使用到一個數據,大機率會使用其附近的數據」,這樣磁盤預讀能充分提升磁盤IO
數據庫的索引最經常使用B+樹:
(1)很適合磁盤存儲,可以充分利用局部性原理,磁盤預讀;
(2)很低的樹高度,可以存儲大量數據;
(3)索引自己佔用的內存很小;
(4)可以很好的支持單點查詢,範圍查詢,有序性查詢;
索引文件
與數據文件
是分離
的MyISAM
的索引文件採用B+Tree
索引葉子節點data域
記錄的是數據存放的地址
主索引(惟一)
與輔助索引(可重複)
在結構上沒有任何區別
數據文件
自己是按照B+Tree
組織的索引結構(主索引:Primary Index
或彙集索引:Clustered Index
),而葉子節點data域
記錄的是完整的數據信息,聚簇索引就是數據的存儲方式(全部的用戶記錄都存儲在了葉子節點),也就是所謂的索引即數據。
必須有主鍵
,若是沒有顯式定義主鍵
或非NULL的惟一索引
,InnoDB會自動生成6 Bytes的ROWID
做爲主鍵輔助索引
(Secondary Index
)也是按B+Tree
組織,葉子節點data域
記錄的是主鍵值
,所以主鍵不宜定義太大
輔助索引
須要遍歷兩遍索引
,首先經過輔助索引
得到主鍵值,再用主鍵值在主索引
中獲取實際數據InnoDB的主鍵索引與行記錄是存儲在一塊兒的,故叫作彙集索引(Clustered Index):
沒有單獨區域存儲行記錄
主鍵索引的葉子節點,存儲主鍵,與對應行記錄(而不是指針)
咱們舉個例子:
CREATE TABLE t ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, key1 INT, common_field VARCHAR(100), PRIMARY KEY (id), KEY idx_key1 (key1) ) Engine=InnoDB CHARSET=utf8;
這個表就包含2個索引(也就是2棵B+樹):
以id
列爲主鍵對應的聚簇索引。
爲key1
列創建的二級索引idx_key1
。
咱們向表中插入一些記錄:
INSERT INTO t VALUES (1, 30, 'b'), (2, 80, 'b'), (3, 23, 'b'), (4, NULL, 'b'), (5, 11, 'b'), (6, 53, 'b'), (7, 63, 'b'), (8, NULL, 'b'), (9, 99, 'b'), (10, 12, 'b'), (11, 66, 'b'), (12, NULL, 'b'), (13, 66, 'b'), (14, 30, 'b'), (15, 11, 'b'), (16, 90, 'b');
因此如今s1
表的聚簇索引示意圖就是這樣:
s1
表的二級索引示意圖就是這樣:
從圖中能夠看出,值爲NULL
的二級索引記錄都被放到了B+樹的最左邊,這是由於設計InnoDB的大叔們有規定:
We define the SQL null to be the smallest possible value of a field.
也就是認爲NULL
值是最小的。
小貼士:原諒咱們把B+樹的結構作了一個如此這般的簡化,咱們省略了頁面的結構,省略了全部的內節點(只畫了了三角形替代),省略了記錄之間的鏈表,由於這些不是本文的重點,畫成若是所示的樣子只是爲了突出葉子節點處的記錄是按照給定索引的鍵值進行排序的。
比方說咱們如今執行下邊這個查詢語句:
SELECT * FROM t WHERE key1 = 53;
那麼語句的執行過程就以下圖所示:
用文字描述一下這個過程也就是:
先經過二級索引idx_key1
對應的B+
樹快速定位到key1
列值爲53
的那條二級索引記錄。
而後經過二級索引記錄上的主鍵值,也就是6
到執行回表
操做,也就是到聚簇索引中再找到id
列值爲6
的聚簇索引記錄。
小貼士:B+樹葉子節點中的記錄都是按照鍵值按照從小到大的順序排好序的,經過B+樹索引定位到葉子節點中的一條記錄是很是快速的。不過因爲咱們並無嘮叨內節點、頁目錄這些東西,因此經過B+樹索引定位到葉子節點中的一條記錄的過程就不詳細嘮叨了,這些東西其實都在《MySQL是怎樣運行的:從根兒上理解MySQL》的掘金小冊裏詳細講述過。
像下邊這個查詢:
SELECT * FROM t WHERE key1 > 20 AND key1 < 50;
它的執行示意圖就是這樣:
用文字表述就是這樣:
先經過二級索引idx_key1
對應的B+
樹快速定位到知足key1 > 20
的第一條記錄,也就是咱們圖中所示的key1
值爲23
的那條記錄,而後根據該二級索引中的主鍵值3
執行回表操做,獲得完整的用戶記錄後發送到客戶端。
而後根據上一步驟中獲取到的key1
列值爲23
的二級索引記錄的next_record
屬性,找到緊鄰着的下一條二級索引記錄,也就是key1
列值爲30
的記錄,而後執行回表操做,獲得完整用戶記錄後發送到客戶端。
而後再找上一步驟中獲取到的key1
列值爲30
的二級索引記錄的下一條記錄,該記錄的key1
列值也爲30
,繼續執行回表操做將完整的用戶記錄發送到客戶端。
而後再找上一步驟中獲取到的key1
列值爲30
的二級索引記錄的下一條記錄,該記錄的key1
列值爲53
,不知足key1 < 50
的條件,因此查詢就此終止。
從上邊的步驟中也能夠看出來:須要掃描的二級索引記錄越多,須要執行的回表操做也就越多。若是須要掃描的二級索引記錄佔所有記錄的比例達到某個範圍,那優化器就可能選擇使用全表掃描的方式執行查詢(一個極端的例子就是掃描所有的二級索引記錄,那麼將對全部的二級索引記錄執行回表操做,顯然還不如直接全表掃描)。
小貼士:咱們這裏仍是定型的分析成本,而不定量分析。定量分析的過程比較複雜,不太小冊裏有寫,有興趣的同窗能夠去看。
因此如今的結論就是:斷定某個查詢是否可使用索引的條件就是須要掃描的二級索引記錄佔所有記錄的比例是否比較低,較低的話說明成本較低,那就可使用二級索引來執行查詢,不然要採用全表掃描。
[1]InnoDB備忘錄 - B+Tree索引
http://zhongmingmao.me/2017/05/13/innodb-btree-index/
[2] 平衡二叉樹、B樹、B+樹、B*樹 理解其中一種你就都明白了
https://zhuanlan.zhihu.com/p/27700617
[3] Does mysql use B-tree,B+tree or both?
https://dba.stackexchange.com/questions/204561/does-mysql-use-b-tree-btree-or-both
[4] MySQL的索引
[5] 數據庫兩個神器【索引和鎖】
http://www.javashuo.com/article/p-pnqixahw-bu.html
[6] 設計 MySQL 數據表的時候通常都有一列爲自增 ID,這樣設計緣由是什麼,有什麼好處?
https://www.zhihu.com/question/28703540/answer/494072901
[7] 機器學習能革了數據庫索引的命嗎?
https://mp.weixin.qq.com/s/o115JjjtUzJZ4MQ9yO-WuQ
[8] 收藏版MySQL語句加鎖分析
[9] 快速理解爲啥這個查詢使用索引,那個查詢不使用索引
https://mp.weixin.qq.com/s/cyr8rW9-iP_N-BWDyqIVEQ
[10] 面試官:爲何 MySQL 索引要使用 B+樹而不是其它樹形結構?好比 B 樹?InnoDB一棵B+樹能夠存放多少行數據?