05 MySQL-初識MySQL-索引-下

04 MySQL-初識MySQL-索引-上 篇中介紹了InnoDB索引的數據結構模型以及索引維護。本篇繼續針對MySQL索引進行...

場景分析
先看一個場景分析:假設我們存在這樣一張表(如下是表的初始化語句):

create table T (
ID int primary key,
k int NOT NULL DEFAULT 0, 
s varchar(16) NOT NULL DEFAULT '',
index k(k))
engine=InnoDB;

insert into T values(100,1, 'aa'),(200,2,'bb'),(300,3,'cc'),(500,5,'ee'),(600,6,'ff'),(700,7,'gg');

如果執行語句,需要執行幾次樹的搜索操作,會掃描多少行?

select * from T where k between 3 and 5;

我們先來看看數據索引的存儲結構(InnoDB的索引組織結構)如下圖:

mysql-B-Page (1).png

我們分析上面語句的執行流程:根據我們之前在04 MySQL-初識MySQL-索引-上提到的二級索引的查詢邏輯,我們可以知道這條語句的執行流程如下:

  1. 在k索引樹上找到k=3的記錄,取得 ID = 300;

  2. 再到ID索引樹查到ID=300對應的T3;

  3. 在k索引樹取下一個值k=5,取得ID=500;

  4. 再回到ID索引樹查到ID=500對應的T4;

  5. 在k索引樹取下一個值k=6,不滿足條件,循環結束。

到此其實我們已經知道,在整個過程中查詢過程讀了k索引樹的3條記錄(步驟1、3和5),回表(回到主鍵索引樹搜索的過程,我們稱為回表)了兩次(步驟2和4)。由于查詢結果所需要的數據只在主鍵索引上有,所以不得不回表。

問題就來了,
查詢范圍越大,回表次數就越多,性能也就越差,有沒有可能經過索引優化,避免回表過程呢?

  • 答案肯定是有的。

一 覆蓋索引

select * from T where k between 3 and 5;

如果執行上述語句加入查詢結果只是select * from T where k between 3 and 5;,只需要查詢ID值,而ID的值已經在k索引樹上了,因此可以直接提供查詢結果,不需要回表。

  • 上述查詢里面,索引k已經“覆蓋了”我們的查詢需求,我們稱為覆蓋索引。

  • 由于覆蓋索引可以減少樹的搜索次數,顯著提升查詢性能,所以使用覆蓋索引是一個常用的性能優化手段。

  • 需要注意的是,在引擎內部使用覆蓋索引在索引k上其實讀了三個記錄,T3~T5(對應的索引k上的記錄項),但是對于MySQL的Server層來說,它就是找引擎拿到了兩條記錄,因此MySQL認為掃描行數是2。

基于覆蓋索引的說明,我們或許想到一個問題,是不是都需要建立聯合索引呢?
場景分析:
一張用戶信息表,是否有必要將身份證號和名字建立聯合索引?

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

  • 身份證號是市民的唯一標識。也就是說,如果有根據身份證號查詢市民信息的需求,我們只要在身份證號字段上建立索引就夠了。而再建立一個(身份證號、姓名)的聯合索引,是不是浪費空間?

  • 如果現在有一個高頻請求,要根據市民的身份證號查詢他的姓名,這個聯合索引就有意義了。它可以在這個高頻請求上用到覆蓋索引,不再需要回表查整行記錄,減少語句的執行時間。

  • 當然,索引字段的維護總是有代價的。因此,在建立冗余索引來支持覆蓋索引時就需要權衡考慮了。

問題:根據覆蓋索引和聯合索引的解說,我們是不是每一種查詢都需要設計一個索引呢?

二 最左前綴原則

如果為每一種查詢都設計一個索引,索引肯定會太多了。而且索引的維護成本就會急劇增長。依據上述用戶表,這樣一個查詢如果通過user身份id查詢user的住址信息?雖然查詢的頻次可能不是高頻,但是同樣龐大的用戶量來說,不能每次查詢都進行全表掃描。但是單獨為一個不太高頻的請求進行創建一個索引(身份證,地址)又會很浪費。這是就需要用到最左前綴原則。

  • B+樹這種索引結構,可以利用索引的“最左前綴”,來定位記錄。

假設我們存在這樣一個用戶名和年齡組成聯合索引(name,age),如下圖所示,(name,age)聯合索引

mysql-compositeIndex-Page.png
  • 索引項是按照索引定義里面出現的字段順序排序的。

  • 當查詢名字全是張四的時候,就可以快速定位到ID4,然后繼續向后遍歷,得到所有的結果。

  • 當查詢為名字第一個字為 (where name like '張%'),這時,也能夠用上這個索引,查找到第一個符合條件的記錄是ID3,然后向后遍歷,直到不滿足條件為止。

  • 不只是索引的全部定義,只要滿足最左前綴,就可以利用索引來加速檢索。這個最左前綴可以是聯合索引的最左N個字段,也可以是字符串索引的最左M個字符。

討論一個問題:在建立聯合索引的時候,如何安排索引內的字段順序。 ?

這里我們的評估標準是,索引的復用能力。因為可以支持最左前綴,所以當已經有了(a,b)這個聯合索引后,一般就不需要單獨在a上建立索引了。因此,第一原則是,如果通過調整順序,可以少維護一個索引,那么這個順序往往就是需要優先考慮采用的。

如果既有聯合查詢,又有基于a、b各自的查詢呢?查詢條件里面只有b的語句,是無法使用(a,b)這個聯合索引的,這時候你不得不維護另外一個索引,也就是說你需要同時維護(a,b)、(b) 這兩個索引。

這時候,我們要考慮的原則就是空間了。比如上面這個市民表的情況,name字段是比age字段大的 ,那我就建議你創建一個(name,age)的聯合索引和一個(age)的單字段索引。

三 索引下推

說到滿足最左前綴原則的時候,最左前綴可以用于在索引中定位記錄。這時,你可能要問,那些不符合最左前綴的部分,會怎么樣呢?同樣以上述tuser表的聯合索引(name, age)為例:

需求:檢索出表中“名字第一個字是張,而且年齡是10歲的所有男孩”。那么,SQL語句是這么寫的:

select * from tuser where name like '張%' and age=10 and ismale=1;
  1. 已經知道了前綴索引規則,所以這個語句在搜索索引樹的時候,只能用 “張”,找到第一個滿足條件的記錄ID3。當然,這還不錯,總比全表掃描要好。

  2. 然后是判斷其他條件是否滿足。

    • 在MySQL 5.6之前,只能從ID3開始一個個回表。到主鍵索引上找出數據行,再對比字段值。
    • MySQL 5.6 引入的索引下推優化(index condition pushdown), 可以在索引遍歷過程中,對索引中包含的字段先做判斷,直接過濾掉不滿足條件的記錄,減少回表次數。

在下面兩個圖里面,每一個綠色箭頭表示回表一次。

mysql-indexpushdown-Page.png
  • 在(name,age)索引里面特意去掉了age的值,這個過程InnoDB并不會去看age的值,只是按順序把“name第一個字是’張’”的記錄一條條取出來回表。因此,需要回表4次。

  • 區別是,InnoDB在(name,age)索引內部就判斷了age是否等于10,對于不等于10的記錄,直接判斷并跳過。在我們的這個例子中,只需要對ID7、ID8這兩條記錄回表取數據判斷,就只需要回表2次。

問題:表結構定義如下

CREATE TABLE `T` (
  `a` int(11) NOT NULL,
  `b` int(11) NOT NULL,
  `c` int(11) NOT NULL,
  `d` int(11) NOT NULL,
  PRIMARY KEY (`a`,`b`),
  KEY `c` (`c`),
  KEY `ca` (`c`,`a`),
  KEY `cb` (`c`,`b`)
) ENGINE=InnoDB;

出于某些原因這個表需要a、b做聯合主鍵,既然主鍵包含了a、b這兩個字段,那意味著單獨在字段c上創建一個索引,就已經包含了三個字段了呀,為什么要創建“ca”“cb”這兩個索引?

如下解釋:為了這兩個查詢模式,這兩個索引是否都是必須的?為什么呢?

select * from T where c=N order by a limit 1;
select * from T where c=N order by b limit 1;

解答:
* 主鍵 a,b的聚簇索引組織順序相當于 order by a,b ,也就是先按a排序,再按b排序,c無序。
* 索引 ca 的組織是先按c排序,再按a排序,同時記錄主鍵(注意,這里不是ab,而是只有b))
* 這個跟索引c的數據是一模一樣的。
* 索引 cb 的組織是先按c排序,在按b排序,同時記錄主鍵。

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

推薦閱讀更多精彩內容

  • 索引 數據庫中的查詢操作非常普遍,索引就是提升查找速度的一種手段 索引的類型 從數據結構角度分 1.B+索引:傳統...
    一凡呀閱讀 3,009評論 0 8
  • 一、MySQL優化 MySQL優化從哪些方面入手: (1)存儲層(數據) 構建良好的數據結構。可以大大的提升我們S...
    寵辱不驚丶歲月靜好閱讀 2,463評論 1 8
  • [TOC] MySQL索引和SQL調優 本文有參考網上其他相關文章,本文最后有附參考的鏈接 MySQL索引 MyS...
    AllenWu閱讀 2,608評論 0 43
  • 索引是應用程序設計和開發的一個重要方面。 若索引太多, 應用程序的性能可能會受到影響。 而索引太少, 對查詢性能又...
    好好學習Sun閱讀 1,050評論 0 4
  • 一年半前由于內心的苦痛難以承受,我開始追求心靈成長。在我自我療愈的過程里,女兒一直陪伴著我的成長。我們的親子關系經...
    楊娟愛的傳播者閱讀 921評論 0 11