一網打盡!關于mysql索引的基礎與高級知識都在這里了

本文取材于《高性能MySQL第三版》的第5章,由于索引使用了較多的比較復雜的數據結構,限于篇幅,本文僅對這些數據結構進行簡單介紹,如果不清楚的可以自行查資料學習

@TOC

當我們使用

select xx from table where id=x

的時候,不知道你有沒有想過,MySql是如何進行查找操作的,今天我就關于這句話,來說說MySql的索引策略,

索引類型

b-Tree索引

  • 介紹

    大多數MySql引擎都支持b-Tree索引,b-Tree是一種類似二叉平衡樹(BST)的數據結構,如圖


    在這里插入圖片描述

    不過,他是一種多叉樹,查找方式和BST并無二致,區別在于他的所有葉子節點在同一高度,非常整齊。從根到葉節點進行搜索

    但是msyql中使用b+樹,b+樹和b樹的關鍵區別是b樹的路徑記錄了實際數據,但是b+樹的路徑沒有關鍵值,只有索引,所有的數據由葉子節點指向,這樣使得b+樹每一層可以記錄更多的索引,減小了層數,同時葉子節點相互連接,形成了鏈表,如圖

接下來我們來實踐下

CREATE TABLE People (
    last_name varchar(50) not null,
    first_name varchar(50) not null,
    dob date not null,
    gender enum('m', 'f') not null,
    key(last_name, first_name, dob)
);
在這里插入圖片描述

其中key(last_name, first_name, dob)表示將last_name, first_name, dob三列作為索引,根據最左優先,先排last_name,最后排dob,如下

在這里插入圖片描述

在英語中,last_name第一個讀,first_name第二個讀,所以A字母開頭的名先排,而Allen Cuba和Allen Kim的last_name一樣,就比first_name,Cuba先排

在這里插入圖片描述
  • 限制

    相信你發現了,雖然b樹和b+樹可以保證查詢速度,但是他們的索引限定了只能從最左邊開始查找,上面的例子也表明,只能先last_name,再first_name,最后dob,如果想查dob則不得不對前兩個還要進行查找,無法跳過

哈希索引

  • 介紹

    哈希是大家的老朋友了,在java中的hashmap相信大家非常熟悉,簡單介紹下

    hash維護了一個槽slot,一般是一個數組,放置每個要保存的數據的哈希碼(hashcode),hashcode的生成方式可自由選擇,關鍵在于不同數據不能重復,一種常見的是ascii碼累加形式

    比如

    "A"的hashcode就是97

    “AB"的hashcode就是97+98*27

    依次類推

    我們來實踐一下

    CREATE TABLE testhash (
        fname VARCHAR(50) NOT NULL,
        lname VARCHAR(50) NOT NULL,
        KEY USING HASH(fname)
    ) ENGINE=MEMORY;
    

<img src="https://img-blog.csdnimg.cn/20200325130844267.png" width=800>
進行插入

<img src="https://img-blog.csdnimg.cn/20200325130831359.png" width=800>

<font color=#FF0000>假設</font>下面的索引hashcode為下

f('a')= 2323
f('c')= 8784
f('e')= 7437

則哈希索引的數據結構如下

槽(Slot) 關鍵字(Key) 值(Value)
2323 a 指向第一行的指針
7437 e 指向第三行的指針
8784 c 指向第二行的指針

因為這是按hashcode從大到小排序,這里如果出現兩個hashcode一樣怎么辦?

這是著名的解決哈希沖突的問題,有分離鏈接法,開放定址法,雙散列法等方法可以自己探索

所以對于下面這行

SELECT lname FROM testhash WHERE fname='a';

Mysql將先計算a的hashcode為2323,并使用這個值在記錄中找到指向第一行的指針,取出第一行的值,就算完成了一次查找

  • 限制

    1. hash結構查找快,在于他的散列,但是成也散列,敗也散列,他無法對于數據排序,如果一個數據庫只有哈希索引,那么他將無法進行order by操作

    2. 不支持比較查詢,>,<,IN()都不行,也不支持范圍查詢

    3. 如果hashcode算法沒選好,將會產生大量的沖突,不得不花費很多精力解決

空間數據索引 (R-Tree)

  • 介紹

    顧名思義,這是以空間換時間的操作,通過<font color=#FF0000>全文索引</font>的方式,可以從全部維度來查找數據,搜索引擎就是這么干的

    在MySql中,只有MyISAM引擎支持全文索引

    MyISAM的全文索引作用對象是一個“全文集合”,這可能是某個數據表的一列,也可能是多個列。具體的,對數據表的某一條記錄,MySQL會將需要索引的列全部拼接成一個字符串,然后進行索引。
    MyISAM的全文索引是一類特殊的B-Tree索引,共有兩層。第一層是所有關鍵字,然后對于每一個關鍵字的第二層,包含的是一組相關的“文檔指針”

  • 限制

    這種樹的限制也是必然的,對于比較有規律的數據,用全文索引就太浪費了,比較適合搜索引擎使用

如何構造高性能索引

應該考慮的

實際上并沒有什么萬能之策,世界上的數據千奇百怪,每種數據的最好索引都是單獨為其設置的,所以,在這里介紹構建高性能索的方法

逐步優化前綴索引

在b-Tree索引中,選擇不同的順序對搜索情況影響很大,除了順序之外,對于單個的列作為前綴索引,到底設置多少長度也是一個需要考慮的問題,如圖,左邊是城市出現的次數,右邊是城市

在這里插入圖片描述

查找特定城市,到底要匹配多少個前綴字母呢?如果我們只匹配三個

在這里插入圖片描述

發現數量前3個字母相同的遠遠超過城市,畢竟London是最多的,也才出現了65次,但是前綴為San的出現了483次??顯然,是因為前綴為San的城市太多了,經過比對,前7個字母作為前綴比較合適


在這里插入圖片描述

當然了,也有例外情況,但是這樣足以一下子確定大致區間,具體哪座城市,再在區間中匹配即可

==此外還可以后綴(比如電子郵件),中綴(比如年月日中按星座查詢)查詢等,也可以將城市的ascii碼來匹配,思路一樣==

覆蓋索引

簡單來說,就是索引包含了我們要查詢的值,這樣我們就沒必要去查表了,直接查索引即可

CREATE TABLE t (
    id INT,
    name INT,
    age INT,
    sroce INT,
    address VARCHAR(20)
)

就拿這個表來說,如果我們判定我們只通過id和name來查人,那么我們只需要使用b-tree作為數據結構,將id,name放在葉子節點處即可

<font color=#FF0000>這也叫索引覆蓋查詢,這種方法沒有任何缺點!!!</font>

但是,<font color=#FF0000>顯然有適用范圍限制</font>,對數據的規律性要求較高,尤其是對于hash索引這種不以b-tree為基礎數據結構的,無法使用。不過可以對數據進行優化,使其滿足使用覆蓋索引的條件即可

壓縮索引

這種方式準確來說叫“前綴壓縮索引”

就是在前綴索引的基礎上對前綴進行壓縮處理,比如對于city的查找我們認為7個字母的前綴比較合適,但是這7個字母的排列是由規律的,比如什么San,Lon就出現得多,kkk就基本不會出現,假設就排了256中可能,那么理論上我們使用log2(256)=8位bit就能表示完所有前綴,也就是只需要一個字節!

或許你已經想到了,具體壓縮方法可以參考<font color=#FF0000>哈夫曼編碼</font>

但是這樣會導致某些操作變慢,所以這是以時間換空間的操作,對于數據量過大的數據庫,可以考慮使用,壓縮算法好的話,甚至可以壓縮90%,速度也不會慢太多

應該小心的

謹慎選擇:多列單獨的索引

顧名思義,在多個列上分別建立索引

代碼執行如下

CREATE TABLE t (
    id INT,
    name INT,
    age INT,
    KEY(c1),
    KEY(c2),
    KEY(c3)
);

這樣非常靈活,既可以按id查詢,也可以按姓名查詢,<font color=#FF0000>但是,這樣將會導致很嚴重的性能問題</font>

我們知道,一山不容二虎,總指揮只能有一個

  1. 如果服務器對多列單獨的索引進行<font color=#FF0000>AND</font>操作,那么為什么不直接構建一個合并的多列索引?

  2. 如果服務器對多列單獨的索引進行<font color=#FF0000>OR</font>操作,那么將會有大量CPU資源消耗在重復的搜索,還有緩存和排序

  3. 優化器不會把上面的成本計算在內,因為他們關心的是讀取的成本,而不是查詢的成本

聚簇索引

什么是聚簇索引?一圖勝千言

在這里插入圖片描述

簡單來說,<font color=#FF0000>聚簇索引中的葉子節點包含了行的全部數據</font>,主鍵索引有一份,二級索引也有一份

這樣子,有以下優點

  1. 相關數據可以集中在一起,物理上也放一起,可以快速存取
  2. 配合B-Tree使用,查詢速度更快

當然,這顯然是利用空間換時間的方法,缺點也很明顯

  1. <font color=#FF0000>比較適用于I/O密集型應用</font>,假如數據已經讀取到內存了,聚簇索引不僅速度不快,還加載了太多的數據
  2. 插入速度依賴于順序,最好只通過主鍵插入數據到表
  3. 數據存放僵化,不方便遷移和更新

這樣可能還有點抽象,我們來看一個表

在這里插入圖片描述

對于col1,我們按列值從小到大排序,也就是說 假設9999行的3最小,0行的99居中,9998行的4700最大

那么在MyISAM(非聚簇索引)的b-tree索引中,他是這樣的


在這里插入圖片描述

而在<font color=#FF0000>InnoDB(聚簇索引)</font>的b-tree中,他是這樣的

在這里插入圖片描述

看到了嗎,這里多了TID和RP隊列和所有的剩余列(這里的剩余列指col2),少了行號。

==非聚簇索引告訴你你想要的值col,再給你個行號,其他東西你順著這個行號找吧==

==聚簇索引則把所有的東西都給你了,放在葉節點中==

所以看你這個數據是否是I/O密集型,選擇聚簇還是非聚簇

冗余,重復索引和未使用的索引

兩種索引有些區別

冗余索引:創建了(A,B)為索引,再創建A為索引,此時A就是冗余索引

重復索引:完全相同的索引

所以,當你要添加索引的時候,必須先確保既不是重復索引,也不是冗余索引,而且

<font color=#FF0000>應該盡量擴展原有的索引,而不是創建新的索引</font>,可參考上面的“多列單獨的索引”

未使用的索引:有些索引是無法用到的,完全是累贅,可以通過一些工具刪除

索引和鎖

為了避免臟讀幻讀不可重復讀,參考這里:

每次讀的時候數據庫都會鎖定相應的地方,優秀的鎖一定是讀什么鎖什么,而差的往往會只讀一行,卻把整個表都鎖住,所以,在保證正確的前提下,盡量減少鎖的粒度和數量

InnoDB只有在訪問行的時候才會對其加鎖,而索引能夠減少InnoDB訪問的行數,從而減少鎖的數量。但這只有當InnoDB在存儲引擎層能夠過濾掉所有不需要的行時才有效。如果索引無法過濾掉無效的行,那么在InnoDB檢索到數據并返回給服務器層以后,MySQL服務器才能應用WHERE子句(19)。這時已經無法避免鎖定行了:InnoDB已經鎖住了這些行,到適當的時候才釋放。

InnoDB在二級索引上使用共享(讀)鎖,但訪問主鍵索引需要排他(寫)鎖。這消除了使用覆蓋索引的可能性,并且使得SELECT FOR UPDATE比LOCK IN SHARE MODE或非鎖定查詢要慢很多。

總結

除了上面這些,同一章中還有維護索引和表,索引案例學習等大量內容,總的來說,索引是一個非常復雜的話題

為了保證其速度,存儲空間,正確性,對表的影響都是可以接受的,設計索引的人員使用了很多種方法,設計了很多的數據結構。上面的這些方法或許終將被拋棄,但是下面的這些原則是不會變的

  1. 單行訪問是很慢的。特別是在機械硬盤存儲中(SSD的隨機I/O要快很多,不過
    這一點仍然成立)。如果服務器從存儲中讀取一個數據塊只是為了獲取其中一
    行,那么就浪費了很多工作。最好讀取的塊中能包含盡可能多所需要的行。使
    用索引可以創建位置引用以提升效率。

  2. 按順序訪問范圍數據是很快的,這有兩個原因。第一,順序I/O不需要多次磁盤
    尋道,所以比隨機I/O要快很多(特別是對機械硬盤)。第二,如果服務器能夠
    按需要順序讀取數據,那么就不再需要額外的排序操作,并且GROUP BY查詢也
    無須再做排序和將行按組進行聚合計算了。

  3. 索引覆蓋查詢是很快的。如果一個索引包含了查詢需要的所有列,那么存儲引
    擎就不需要再回表查找行。這避免了大量的單行訪問,而上面的第1點已經寫明
    單行訪問是很慢的。

寫在最后,作者是一個喜歡讀書的人,尤其是社科和歷史,覺得好的話可以關注一下【小松與蘑菇】公眾號,編程是手段,生活幸福是目的,大家加油

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容