MySQL之索引系列(二)

有了索引必定會增長查詢速度麼,如何利用好索引呢html

如今有如下表結構及數據,執行 select * from T between 3 and 5,須要執行幾回樹的搜索,會掃描多少行?
圖1 表的結構:
mysql

表結構

圖2 表中數據:
sql

表中數據

圖3 InnoDB的索引組織結構:
數據庫

InnoDB的索引組織結構

由圖1及圖3可知,ID爲主鍵索引,k爲非主鍵索引,K的葉子節點值爲主鍵ID。
如下是語句的執行過程:性能優化

  1. 在k索引樹查找k=3的記錄,獲得ID=300;
  2. 根據ID=300查找對應的R3;
  3. 在k索引樹查找下一個k=5的記錄,獲得ID=500;
  4. 根據ID=500找到R4;
  5. 在k索引樹找下一個k=6,不知足,退出。

從非主鍵索引回到主鍵索引樹搜索的過程,稱之爲回表。以上過程當中,讀了k索引樹的三條記錄(步驟一、三、5),回表了兩次(步驟二、4)。 在引擎內部經過索引k上實際上是讀了三個記錄:k=三、四、5,可是對於MySQL的server層來講,他在找引擎拿到了兩條記錄(具體查找過程當中server與引擎的執行過程見一條SQL查詢語句的執行過程 ),所以MySQL認爲掃描行數爲2。具體如何查看掃描行數後面的會介紹。
瞭解了回表的過程,下面來看如何優化索引來避免回表數據庫設計

覆蓋索引

上文執行的SQL語句若是是 select ID from T where k between 3 and 5,只是請求ID值,那麼這個值已經存在於k索引樹上,已經能夠直接提供結果,不須要回表。也就是說,新的SQL中索引k已經 "覆蓋了" 語句的查詢需求,以上稱之爲覆蓋索引。
索引覆蓋能夠減小樹的搜索次數,以此來提高查詢性能,因此使用覆蓋索引是常見的性能優化手段post

基於覆蓋索引,來看一個新的例子:
有一個用戶表,定義以下:性能

CREATE TABLE `tuser` (
`id` INT ( 11 ) NOT NULL,
`id_card` VARCHAR ( 32 ) DEFAULT NULL,
`name` VARCHAR ( 32 ) DEFAULT NULL,
`age` INT ( 11 ) DEFAULT NULL,
`ismale` TINYINT ( 1 ) DEFAULT NULL,
PRIMARY KEY ( `id` ),
KEY `id_card` ( `id_card` ),
KEY `name_age` ( `name`, `age` ) 
) ENGINE = INNODB
複製代碼

已知經過身份證號是惟一標識,爲了知足根據身份證號查到一個用戶的全部信息,只須要對身份證號創建索引就夠了。在創建一個(id_Card,name)的聯合索引,究竟有沒有必要?學習

若是如今有這樣一個高頻請求:根據身份證號找到用戶的姓名,那麼這個聯合索引的意義就體現出來了,這個高頻的請求可以用到覆蓋索引,由於在(id_Card,name)這個聯合索引樹上直接能根據身份證號查到姓名,不用在拿到主鍵ID後回表查找整行記錄,減小語句的執行時間。 覆蓋索引的目的就是儘可能可以一次查到須要數據,避免有回表過程。優化

最左前綴原則

按照剛剛的需求,爲了根據身份證號查到姓名,須要建立(id_card,name)聯合索引,若是如今新的需求須要根據身份證號查到家庭住址,總不能再添加一個(id_card,addr)聯合索引,爲了每一種查詢都設計一個索引,顯然是不現實的。 這時候應該怎麼作呢?
結論:B+樹這種索引結構,能夠利用索引的"最左前綴"來定位記錄

以聯合索引("name",age)爲例
圖4 (name,age)索引示意圖:

圖 4 (name,age)索引示意圖

由上圖可知索引項是按照索引定義的字段順序排序的。

若是要查詢 "name = '張四'" 的全部結果時,會快速定位到D4,而後向後遍歷到D5結束,返回結果。
若是想查找全部姓張的結果,則條件爲"where name like '張%'" ,此時仍是能夠用上這個聯合索引,定位到D3,而後向後遍歷,直到D5結束,返回結果。

由上面兩個查詢可知,只要知足最左邊的N個字段或者M個字符,就能夠用這個索引來查詢。

以上結論又引起一個新的問題:考慮到最左前綴原則,創建聯合索引時,內部的字段順序該如何安排?

  1. 若是已經存在(a,b)聯合索引,則不須要對a字段創建索引。若是可以調整聯合索引的順序,來減小一個索引,name這個順序應該被優先考慮,以此來提升索引的複用能力。 回到本節開頭的問題:沒有必要爲(id_card,addr)建一條索引。能夠複用已經存在高頻使用的(id_card,name)聯合索引,在根據id_card查找地址時,也可以根據最左前綴原則匹配到(id_card,name)索引,以此來增長查找速度。

  2. 對於id_card和name字段,能夠創建三個索引搜索樹: 對id_card和name分別創建索引、對(id_card,name)創建聯合索引。根據上一條原則,能夠經過調整聯合索引的字段順序來減小一個索引,即保留"(id_card,name)和name索引"或者"保留(name,id_card)和id_card索引"。這時須要考慮空間的佔用狀況了,顯然id_card要比name字段大,那就儘可能複用id_card,建議使用(id_card,name)聯合索引和name單字節索引。

索引下推

圖 4 (name,age)索引示意圖
仍是以上圖爲例,分析如下SQL執行過程

select * from tuser where name like '張%' and age = 23;    
複製代碼
  • 在MySQL5.6以前,經過最左前綴原則定位到D3,而後從D3開始一個一個回表,到主鍵索引樹上找到對應行對比age字段的值,直到D5結束,不能匹配到'張%'後退出查詢,以上共回表3次。雖然比遍歷全錶快,可是仍是作了屢次無用的回表操做。

  • 在MySQL5.6及之後,引入了索引下推優化(index condition pushdown),在對聯合索引遍歷的過程當中,根據索引中包含的字段對查詢語句的其餘條件進行判斷,以此過濾不知足條件的記錄,減小回表次數。對於上述例子來講,就是innoDB在聯合索引搜索樹根據最左前綴定位到D3,接着判斷age字段不等於23,繼續向後遍歷,D4中name符合"張%",但age不等於23,繼續直到D5,name與age都符合,這時纔會回表。以上過程只須要回表1次。

總結

三種優化方式:

  • 覆蓋索引
  • 最左前綴原則
  • 索引下推
    以上三種方式都有同一個原則,也是數據庫設計的重要原則之一:儘可能少地訪問資源達到一樣的效果

查詢語句中where裏面順序和聯合索引順序不一致,優化器會自動作優化。


本文爲極客時間《MySQL實戰45講》 的學習筆記,其中含有部分原文,若有侵權行爲請聯繫我馬上刪除
再次感謝丁奇大佬
第一節:一條SQL查詢語句的執行過程

相關文章
相關標籤/搜索