一個SQL查詢走索引時涉及到的最左前綴原則

一個SQL查詢走索引時涉及到的最左前綴原則

 

在講解最左前綴原則之前,先複習一下MySQL索引的重要基礎知識(下面都將基於InnoDB存儲引擎下的索引規則)

索引類型

主鍵索引

InnoDB存儲引擎使用B+樹建立索引,主鍵索引的非葉子結點存放主鍵字段的值,通過主鍵中的字段構建B+樹,葉子結點存放對應主鍵的整一條記錄的信息(因此主鍵索引也稱爲聚集索引),每張表只能建立一個主鍵索引(聚集索引) 。

輔助索引

輔助索引(Secondary Index),也叫做二級索引,也是通過B+樹建立,與主鍵索引的唯一不同之處在於,葉子結點存放的是對應行的主鍵值,而不是行數據 (因此也叫做非聚集索引,獲取主鍵值之後,需要再次去主鍵索引表中查詢該主鍵對應的記錄,獲取其葉子結點存儲的記錄內容,相當於要搜索兩張索引表)

舉個例子

這裏給出一張表,id字段爲主鍵索引,age字段爲普通索引,然後插入一些數據,然後給出InnoDB爲其維護的兩個邏輯上的索引文件結構。

create table T(
  `id` int primary key,
  `name` varchar(11) not null,
  `age` int not null,
  index(age)
) # 5.5以後默認是InnoDB存儲引擎
# 插入了四條數據:(1, 小明, 15)、(2, 小紅, 20)、(3, 小蘭, 16)、(4, 小金, 18)

image-20220412194122065

下面給出兩個查詢語句並分析索引執行情況

select * from T where id = 1 # 按照左側主鍵索引搜索樹,搜索到id爲1的葉子結點,獲取其中的記錄數據
select * from T where age = 15 # 先按照右側age建立的輔助索引樹找到age=15對應記錄主鍵id值等於1,然後再去左側主鍵索引搜索樹搜索id=1的這條記錄

通過分析第二條SQL,我們得出結論,對於走輔助索引的查詢,必然會二次查詢主鍵索引樹(當然有特殊情況,下面講) ,一張表只有一個主鍵索引,但是可以建立很多的輔助索引,且輔助索引的葉子結點裏存放着主鍵值,那麼如果主鍵是字符串類型或者長度很長,那麼必然會導致輔助索引佔用的空間增加,所以自增主鍵往往是一個常用的選擇。

覆蓋索引

那麼所有使用輔助索引的SQL查詢語句都必須兩次回表嗎?當然有特殊情況,如果輔助索引樹的葉子結點中的字段,已經覆蓋了需要查詢的所有字段,則不需要回表(回表的目的是獲取輔助索引樹中沒有的字段數據),覆蓋索引我更願意稱之爲索引覆蓋,它還是歸屬於輔助索引。

select id from T where age = 15 # 對於這個查詢,將查詢的字段只要求id,則在搜索完右側age的輔助索引樹之後,即可獲得到id=1,無需回表

聯合索引

聯合索引依舊是輔助索引的一種情況 (不是主鍵索引就都歸屬於輔助索引) ,輔助索引可以在多個字段之間建立,如果第一個字段相同則比較第二個字段,依次類推建立索引搜索樹結點之間的先後關係,也就是說索引項按照索引定義的字段順序排序 (後面要講到的最左前綴原則就是在此基礎上來分析的) ,下面舉個例子,還是藉助上面這張表,但是輔助索引不是單單age字段建立,而是name和age共同建立。

create table T(
  `id` int primary key,
  `name` varchar(11) not null,
  `age` int not null,
  key `name_age` (`name`, `age`)
) # 5.5以後默認是InnoDB存儲引擎
# 插入了四條數據:(1, 小紅, 16)、(2, 小紅, 15)、(3, 小蘭, 16)、(4, 小金, 16)

image-20220412204112281

下面給出一條針對這個name_age的聯合索引的查詢語句

select id from T where name = '小紅' and age = 15 # 通過上面學習索引覆蓋的知識點,你應該能分析出這條sql只會搜索右邊的聯合索引樹,獲得到id之後不需要再去回表搜索主鍵索引樹

最左前綴原則

概念

還是以上面的這個聯合索引爲例,如果我的sql語句如下:

select id from T where name = '小紅' # name_age索引樹在滿足name有序的前提下,滿足age有序,因此對單一name字段的查詢也可以走這個索引,找到滿足條件的第一條記錄的id,然後按順序向後遍歷找到其他滿足要求的記錄id
select id from T where age = 15 # age字段是name_age索引的第二個字段,在name無序的前提下,age的有序是無意義的,索引無法利用這個聯合索引,需要全表掃描獲取滿足age=15的記錄
select id from T where name like '小%' # 首先name字段是name_age輔助索引的左側第一個字段,且通配符%在右側,因此也可以滿足最左前綴原則,在查詢時走這個輔助索引,定位到第一個滿足name='小%'的記錄的id,然後向後遍歷找到其他滿足條件的記錄
# ps.這三個語句都是不用回表的

最左前綴原則:只要你的查詢語句涉及的字段滿足已有輔助索引的左側出現順序(或者匹配字符串的左側n個字符),而不出現越過某個字段的情況,查詢就可以走這個輔助索引,這就是最左前綴原則,查詢將返回第一個滿足查詢條件的記錄對應的主鍵id,根據情況看是否需要回表搜索主鍵索引樹。

提醒:爲了方便,我上面作的B+樹索引樹葉子結點之間的雙端鏈表結構沒有標出,這裏提醒一下,因爲講最左前綴原則的例子中出現了找到第一個滿足條件的記錄id之後,按順序向後遍歷的情況,這是得益於B+樹葉子結點相互串連的結構

聯合索引字段順序

通過上面的分析,對於一個輔助索引(a, b)來說,不需要爲a單獨再建立索引,但可以再給b單獨建立輔助索引(因爲b爲查詢條件不滿足輔助索引的最左前綴原則),那麼思考一下,如果調整聯合索引的順序爲(b, a),那麼就不用單獨爲b建立輔助索引,而需要爲a建立輔助索引。此時(a, b)b方案與(b, a)a方案都能滿足對(a,b)ab三個字段的查詢調用輔助索引,差別在於哪?

空間!這裏比較好的方案是看a與b哪個字段長,則將其放在聯合索引的前部,而需要額外建立輔助索引的用較短的字段,這樣綜合可以減少空間的使用(如果a字段長,則必有2a+b > 2b+a的空間使用)

索引失效

輔助索引會在最左前綴原則的基礎上,一直向右匹配直到遇到範圍查詢(>、<、between、like)就停止匹配。範圍列可以用到索引,但是範圍列後面的列無法用到索引。舉個例子:

create table T(
  `id` int primary key,
  `name` varchar(11) not null,
  `age` int not null,
  `sex` varchar(11) not null,
  key `name_age` (`name`, `age`)
) # 5.5以後默認是InnoDB存儲引擎
# 插入了四條數據:(1, 小紅, 16, 女)、(2, 小紅, 15, 女)、(3, 小蘭, 16, 女)、(4, 小金, 17, 男)

image-20220413101239947

分析下面這條sql的索引調用情況:首先是匹配name like '小%',可以走右側輔助索引樹,找到id=2的記錄,然後順序向後掃描滿足age=16的記錄,並不能繼續利用聯合索引中age這個部分,最終得到id=1id=3的兩條記錄,最後需要回表搜索主鍵索引樹,因爲這個聯合索引並沒有完全做到索引覆蓋,缺少了sex字段。

解釋:因爲滿足name like '小%'的記錄可能有多條,而age字段的有序是建立的name有序的基礎之上,上圖中(小紅, 15) (小紅, 16) (小金, 17) (小蘭, 16),單獨看age字段之間是無序的,因此在滿足條件的name字段是多個的時候,age字段的索引就喪失功能了,只有當name字段匹配的結果唯一,age字段的有序纔有意義。

select * from T where name like '小%' and age = 16

索引下推(MySQL5.6)

對於上面這個查詢語句,因爲sex字段是沒有被聯合索引覆蓋,因此需要二次回表查詢主鍵索引樹,但是顯然age字段的值是聯合索引的一部分,且查詢的是age等於16,而有些記錄必然不符合匹配,那還有必要回表嗎?

索引下推:MySQL5.5以及之前的版本中,在滿足範圍匹配name like '小%'之後,並不會繼續判斷後面個age字段,直接就回表了,而從MySQL5.6開始,InnoDB存儲引擎在匹配到滿足name like '小%'之後,無法繼續使用最左前綴原則的字段(如本例的age)依舊在聯合索引中,則會根據這些字段多做一些過濾,不滿足條件的記錄將不會回表查詢,減少了二次搜索的次數。

索引重建

這裏補充一點額外的知識,之前聽聞過一個索引使用的中出現的問題案例:

有一個線上的記錄日誌的表,定期會刪除早期的數據,經過一段時間的維護,這個表中存放的記錄空間穩定在10G,但是索引佔用空間有30G,一共40G空間。

原因:InnoDB存儲引擎表就是索引組織表,記錄數據存放在主鍵索引葉子結點上,這張表會被不斷插入日誌記錄,且定期刪除日誌記錄,會導致維護索引的B+樹頻繁發生頁的分裂,導致頁空間中出現浪費的空間,提高了索引的佔用空間。

解決:可以通過重建索引的方式,刪除之前的舊索引,並重新創建這個索引,因爲數據已經在表中,因此重建索引的過程會將表中的數據按順序插入,使得頁面結構重新恢復緊湊(當然具體重建索引的方案需要結合更多的因素去分析,並不是定期重建索引就一定是好的,這裏不多深究)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章