寫在前面
想要做好后台開發,終究是繞不過索引這一關的。先問自己一個問題,InnoDB為什么選擇B+樹作為默認索引結構。本文主要參考MySQL索引背后的數據結構及算法原理和剖析Mysql的InnoDB索引。
索引
當數據量到達一定規模時,我們通常會對經常使用的字段建立索引,來加快數據的查詢。首先需要強調的是索引的本質是數據結構,前輩們經過不斷完善得到了幾種復雜度較低並且能夠降低磁盤IO的數據結構,這里要說的是B樹與B+樹,他們被廣泛應用在文件系統與數據庫系統中。
B-Tree
B樹邏輯上是一顆多叉樹,3階B樹如下:
m階B樹滿足以下幾個條件:
- 非葉子節點最少有m/2顆子樹(即B樹的度為m/2)
- 葉子節點在同一層,每個節點最多有m-1個升序排列的key(索引列)和m個指針,key與指針相互間隔
搜索二叉樹的查詢復雜度為O(log2N),而B樹的復雜度為O(logm/2N),對於N=62*1000000000個節點,如果度為1024,則logM/2N <=4,可以說它是效率很高的數據結構。
B+樹
B+樹是B樹的變種,區別有三點:
- 非葉子節點只存儲key,不存儲data;葉子節點存儲所有key與data,不存儲指針
- 葉子節點增加了順序訪問指針
- 每個節點最多有m個升序排列的key
上述區別換來的優點包括:
- 非子節點可以存放更多的key,具有更好的空間局部性,提高緩存命中率
- 葉子節點相鏈便於區間查找,順序查找替代B樹的遞歸查找。
為什么選擇B+樹
首先要意識到數據檢索的時間主要耗費在磁盤IO(尋道時間、旋轉時間)上,因此要盡量減少IO次數。對樹形結構的數據來說,樹的每一層代表需要一次磁盤IO查詢,因此設計了“扁平”的B樹與更扁的B+樹。另外,由著名的局部性原理,訪問的數據通常比較集中,磁盤每次IO時會預讀數據,預讀的長度為頁(4k)的整數倍,B/B+樹新建節點會申請一個頁的空間,因此取一個節點只需要一次IO(非葉子節點可存儲到內存中)。
索引創建過程
mysql創建索引是通過online create index,減少業務停寫時間,創建索引期間業務能正常工作。
步驟:
- 等待當前所有事務執行結束;新事務更新數據會把新建索引記錄到Row Log中
- 構建索引,從主表讀出數據並排序。使用臨時文件進行外部排序方式,單線程兩路歸並。
- 把增量數據從Row Log更新到索引表中
MySQL存儲引擎
首先區分聚簇索引(按主鍵聚集)與非聚簇索引:
- 二者都使用B+樹作為數據結構
- 聚簇索引的data存於主鍵索引的葉子節點中,得到key同時得到data,非聚簇索引數據存於獨立的地方,葉節點保存的是數據的地址。
- 聚簇索引的輔助鍵索引(非主鍵索引,例如employee表中對name建索引)葉節點存儲主鍵而非數據(為了節省空間,缺陷是需要到主鍵索引中二次查詢);非聚簇索引葉節點保存數據的地址。
聚簇索引的優勢在於找到主鍵同時得到data,省去二次磁盤IO;另外B+樹在插入或刪除節點時周圍節點地址會發生變化,對非聚簇索引來說需要更新所有B+樹的地址指針,增加開銷。
InnoDB
InnoDB使用聚簇索引(MyISAM使用非聚簇索引),其磁盤管理邏輯單位是Page(不同於上述內存中的頁!),每個Page大小為16k,使用32位int標識,對應innoDB最大64TB的存儲容量。
每個Page包括頭部、主體、尾部三部分:
其中頭部包括id與相鄰Page指針(構成雙向鏈表);
主體即B+樹節點的存儲,其中包括很多Record(節點)包括四類:
- 主索引非葉子節點:定位Page
- 主索引葉子節點:包括key與該key對應的所有列(mysql表中的一行)
- 輔助索引非葉子節點:定位Page
- 輔助索引葉子節點:包括索引鍵值與主鍵值(key)
主鍵選擇
因為數據存於主索引中,要求一個節點的各條數據記錄按主鍵順序存放,當一頁達到裝載因子(15/16)會自動開辟新的頁。如果使用自增主鍵,每次插入新紀錄都順序添加到索引節點的后續位置,否則會節點中key會一直移動。
最左匹配原則
在聯合索引中對a,b兩個字段建立索引(a, b),在查詢時只有包括a時才會查詢索引。
如上圖(a, b)聯合索引,在a相同時,b按順序排列。在遇到范圍查詢時之后的字段會停止匹配。因為a是范圍,b無序。