項目中一直使用Mysql,對於慢sql優化也一直在做,但是一直沒有梳理清楚,這里簡單總結一下
首先看一下mysql為什么要使用索引
1)索引是幫助Mysql高效獲取數據的 排好序的 數據結構
2)索引存儲在文件里
首先說明一下,Mysql是使用B+樹作為索引的
在沒有索引的情況下,如果要找到一條記錄的化,是通過全表掃描的
一張數據表中記錄了分數,有兩個字段,id,core:
如果要查找core=5 的記錄,where core=5 ,按照全表掃描順序查找從第一條記錄開始,需要查詢6次才可以找到
如果我們對core字段加上索引后,假設使用的是最簡單的二叉樹作為索引的存儲方式,同樣的如果查找 where core =5 的記錄,按照二叉樹的存儲結構,如下圖
那么我們查找3次就可以找到了
那為什么Mysql中不使用二叉樹作為索引呢,那就來看看二叉樹的特點
(一)二叉樹
二叉樹是一種比順序結構更加高效的查找目標元素的結構,從父節點開始,和目標元素比較,如果相等返回當前節點,如果目標元素值小於當前節點,則移動到當前節點的左側子節點比較,如果目標元素值大於當前節點,則移動到當前節點的右側子節點比較,反復操作,最終找到目標元素節點的位置后返回,二叉樹的查找時間復雜度可以達到O(log2(n))。
但是二叉樹有一個致命的缺點,如果我們的目標元素是有規律遞增或者遞減的,這樣出來的二叉樹就會失去平衡,變成了一個線性鏈表結構,比如使用二叉樹作為索引,那么我們將數據庫主鍵ID這一列建立索引,得到的索引的數據結構就變成下面這樣,這種情況下,查找數據和沒有索引是一樣的效果,那么索引就起不到作用了。
針對上面的這種場景,我們很容易想到那為什么不用可以保持平衡的二叉樹呢,比如說紅黑樹,那么我們就來看看紅黑樹的特點
(二)紅黑樹
紅黑樹是一種平衡二叉樹,它繼承了二叉樹的優點,由解決了二叉樹遇到的自增數據索引失效的問題,因為紅黑樹的會對樹的結構進行調整,進行左旋或者右旋及顏色變換等操作,始終保證 左子節點數<父節點數<右子節點數
紅黑樹不作為Mysql索引的原因是,當數據量大 時候,樹的深度也很大,每個父節點最多只能有兩個子節點,如果表中有很多數據,那么樹的深度很大,達到十幾或幾十層,后續會了解到這樣的深度對於Mysql的磁盤尋址非常不利,也是相當耗時的。
那問題來了,如果是因為樹的深度太高,查找數據不方便,那為什么不用可以直接定位到某條數據的hash算法作為Mysql的索引呢,那么我們來看看Hash的特點
(三)Hash算法
對數據進行hash,也就是散列運算,然后將hash結果作為文件指針,可以從索引文件中獲得數據的文件指針,再到數據文件中獲取到數據,按照這種結構,我們很快能定位出來某一條數據的位置,查詢的效率非常高,那么它的問題點在哪呢,那就是不支持范圍查詢,分頁或者大於 小於某個范圍的查詢都是無法支持的,只能支持固定的 字段名 = 目標值的場景,同樣也不適合Like這種模糊查詢,所以這種算法肯定是不適合作為數據庫的索引的。
那我們再來看看平衡二叉樹的問題
平衡二叉樹的要求是 節點的子節點高度差不能超過1,如上圖中節點20,左節點高度為1,右節點高度為0,高度差為1,所以上圖可以定義為一個平衡二叉樹,保證二叉樹平衡的方法就是通過左旋或者右旋等操作
上圖中保存的id的索引,如果要查找id=8的數據,首先要把根節點加載到內存中,然后進行比較,8比根結點的10小,所以要加載10的左節點就是5對應的磁盤塊2,把5加載到內存中進行比較,8和5比較,需要加載5的右節點也就是8的索引對應的數據,發現命中,整個過程中進行了3次IO。
那么現在來看看怎么找到索引對應的數據呢
索引保存數據的方式一般有兩種,第一種是在節點的數據區保存ID=8的行數據的所有數據具體內容,另一種方式是在節點的數據區保存的是真正保存數據的磁盤地址。
平衡二叉樹解決了線性鏈表的問題,查詢效率也還可以,但是Mysql不選用這種結構作為索引的原因總結一下:
1)搜索效率不足,在樹的結構中,樹的深度決定了搜索的IO次數,上面的查找id=8的數據,就進行了3次IO,當數據量達到百萬級別的時候,樹的高度導致的IO次數就很恐怖了。
2)查詢的效率不穩定,如果查找的數據正好落在根節點,那么只需要一次IO,如果是在葉子節點,那么就需要多次IO。
3)節點存儲的數據內容太少,沒有很好的利用操作系統和磁盤數據交換特性,也沒有很好的利用磁盤IO的預讀能力。操作系統和磁盤之間一次數據交換是以頁為單位的,一頁 = 4K,也就是每次IO交互操作系統會將4K的數據加載到內存中,但是在二叉樹的每個節點的結構中只保存了一個關鍵字,一個數據區,兩個子節點的引用,並不能夠填滿4K的數據量,也就是辛辛苦苦做了一次IO操作,卻只加載了一個關鍵字,在數的高度很高,搜索的數據又是在葉子節點,取一個關鍵字需要做很多次的IO
有沒有辦法解決這種問題呢?
有的,一種多路平衡查找樹(Balance Tree),B Tree是一個絕對平衡樹,所有的葉子節點在同一個高度
看一下定義,上面的這個樹是一個 2-3樹(每個節點存儲2個關鍵字,有3路),每個節點保存的關鍵字個數和路數關系為:關鍵字個數=路數-1
假設從上圖中查找id=28的數據,B Tree的查找過程是:首先把根結點加載到內存中,加載了17和35兩個關鍵字, 比較,如果X<17,則指向P1,如果X=17,命中返回,如果 17<X<35,則指向P2,如果X=35,則命中返回,如果X>35,則指向P3,按照如上規則遞歸查找,首先找到指針P2對應的節點,直到命中28對應的節點數據后,就去加載28對應的數據,找到28對應 數據區,數據區中存儲的是具體的數據或者是指向數據的指針。
這種數據結構能夠很好的利用操作系統和磁盤的交互特性,Mysql為了能更好的利用磁盤的預讀能力,將頁的大小設置為16K,就是將一個節點(磁盤塊)的大小設置為16K,一次IO將一個節點(16K)內容加載到內存中,假設關鍵字的類型是int,4個字節,每個關鍵字對應的數據區也為4個字節,不考慮子節點應用的情況下,上圖中每個節點大約能夠存儲(16*1000)/8 = 2000個關鍵字,那么對應的就是2001個路數,對於這種有2001個路數的B樹,三層的高度能夠搜索到的關鍵字的個數是遠遠大於普通的二叉樹的
在B樹保持樹的平衡的過程中,每次關鍵字的變化都會導致機構發生很大的變化,這個過程是特別浪費時間的,所以創建索引一定要創建合適的索引,而不是把所有的字段都建立索引,創建冗余的索引只會對數據的增加、刪除、修改增加更大的性能消耗。
既然B樹已經能很好的解決問題了,為什么還需要用B+樹呢?
首先來看一下B+樹的概念
B+樹是B樹的一個變種,它不再遵循 關鍵字個數=路數-1 這個規則,數據的檢索規則是采用的左閉合取件,路數和關鍵字的個數關系為1:1,看下圖:
如果查找id=1的數據,搜索的規則是 1<= X <28 ,找到P1, 28<=X <66 ,找到P2, 66<=X,找到P3,最終是在葉子節點 節點1的數據區中獲取真正的數據。
總結一下B樹和B+樹的區別:
1、B+樹的關鍵字的搜索采用的是左閉合區間,之所以要這樣是因為要最好的去支持自增的id,也是Mqsql設計的初衷,保證id=1命中的情況下,也去繼續往下查找,知道找到葉子節點中1
2、B+樹中的根結點和支節點中沒有數據區,關鍵字對應的數據只保存在葉子節點中,所以只有葉子節點中的關鍵字數據區才會保存真正的數據內容或者數據對應的地址,但是在B樹中,如果根結點命中,是直接返回的,B+樹中,葉子節點不會保存子節點的引用
3、B+樹的葉子節點是順序排列的,並且相鄰節點之間是順序引用的關系,葉子節點之間通過指針相連
Mysql為什么最終選擇了B+樹作為索引的數據結構
1.B樹能解決的問題,B+樹都能解決,且能夠更好的解決,降低了樹的高度,增加節點的數據存儲量。
2.B+樹的掃庫和掃表能力更強,如果根據索引去根據數據表掃描,對B樹掃描,需要整顆樹遍歷,B+樹只需要遍歷所有的葉子節點
3.B+樹的磁盤讀寫能力更強,根結點和支節點不保存數據區,所有的根結點和支節點天同樣大小的情況下,保存的關鍵字更多,葉子結點不存子節點的引用,所以,B+樹讀寫一次磁盤加載的關鍵字更多
4.B+樹具有天然的排序功能
5.B+樹的查詢效率更加穩定,每次查詢數據,查詢IO次數是穩定的。