MySQL 索引类别与索引使用指南
文章目錄
- 概述
- MySQL 索引類型
- MySQL 索引方法
- BTREE 方法
- HASH 方法
- 主鍵構成的索引結構
- 主鍵索引的優點
- 主鍵索引的缺點
- 依賴順序插入
- 更新代價高
- 索引使用指南
- 索引樹回顧
- 索引樹排序規則
- 最左前綴法則
- 最左前綴法則的產生依據
- 最左前綴法則延申
- 字段書寫順序不影響最左前綴法則
- 最左前綴法則總結
- 索引字段匹配時不要進行操作
- 大于小于符號范圍查詢對于索引后面字段的影響
- 其他的規則
- 綜合評估索引是否使用
- 值辨別度低的字段不使用索引
- 通配符開頭的模糊查詢不使用索引
- 不等于操作不使用索引
概述
在日常開發過程中,為 MySQL 表中的字段建立索引是我們常用的性能優化手段。使用開發工具添加索引也很方便,使用次數多了閉著眼睛都能完成操作。
但是細心一點的小伙伴會發現,索引并不是那么簡單:
- 除了普通索引或者唯一索引,還有哪些類型的索引,它們的作用分別是什么呢?
- BTREE,HASH 索引方法的適用場景分別是什么呢?
- 為什么字段上已經建立了索引,但是實際執行包含該字段的 SQL 時卻沒有走索引?
- 索引有哪些使用規則呢?
下面請跟隨著這篇文章,一起得到這些問題對應的答案。
MySQL 索引類型
MySQL 的索引類型,主要包括:
- NORMAL:普通索引,普通索引使用方法沒有做特殊限制,因此應用范圍比較廣
- UNIQUE:唯一索引,在普通索引的基礎上,增加了唯一約束功能。唯一約束表示建立在這種索引上的字段值將會在表中唯一(一個或多個字段聯合唯一)
- FULLTEXT:全文索引,必須建立在**字符或文本類型(如 varchar)**的列上,可以實現在大文本中搜索指定關鍵詞的功能
- 注意,使用全文索引時,必須使用 match 和 against 關鍵字進行操作
- SPATIAL:空間索引,必須建立在空間數據類型(如 geometry)且非空的列上,可以實現空間數據查詢的功能
上面就是 MySQL 所有的索引類型了,在實際使用中,需要結合實際情況需要來選擇合適的索引類型。
MySQL 索引方法
MySQL 的索引方法,主要包括 BTREE 和 HASH。
顧名思義,BTREE 方法,就是通過構建 B+ 樹的方法來組織索引結構;而 HASH 方法,就是通過構建哈希表的方法來組織索引結構。
BTREE 方法
BTREE 方法,是通過構建 B+ 樹的方法來組織索引結構的。例如有這樣一張表:
CREATE TABLE `user` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主鍵',`name` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '名稱',`age` smallint NOT NULL COMMENT '年齡',`register_date` date NOT NULL COMMENT '注冊日期',`sex` tinyint NOT NULL COMMENT '性別',`address` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '地址',`phone` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '電話',PRIMARY KEY (`id`),KEY `idx_name_age_registerdate` (`name`,`age`,`register_date`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;可以看到,在 user 表中,有一個由 name、age、register_date 三個字段聯合組成的普通索引。現在假設表中有一些這樣的數據:
那么由 BTREE 方法組織的這個索引的結構示意(圖只為示意,請不要較真名字的真實字典序和實際的 B+ 樹插入邏輯)圖為:
可以看到,這就是一顆 B+ 樹,且索引關鍵字之間的字段值與其在數中的實際順序的對應關系一目了然,這張圖在后面講解索引規則的時候也還會用到。
HASH 方法
HASH 方法(下面簡稱哈希索引)是通過構建哈希表的方法來組織索引結構的,解決哈希沖突的方法為鏈地址法。
既然哈希索引是基于哈希表實現的,那么它就繼承了哈希表的所有優點和缺點。那么我們可以得出:
- 哈希索引只在等值查詢時才有效,無法在范圍查詢時生效,也不能應用于排序
- 哈希索引不支持部分索引列匹配查找,通俗地說就是,如果想要用哈希索引,就要用全,而不是只用一部分字段
- 當哈希碰撞的情況很嚴重時,對哈希索引的維護和查詢操作性能都會降低
主鍵構成的索引結構
InnoDB 存儲引擎主鍵字段構成的索引結構,是在 UNIQUE 索引類型 + BTREE 索引方法基礎上,將實際數據存儲在了葉子節點上。這樣的索引結構,我們稱為主鍵索引,或者聚簇索引。
聚簇,這個詞的含義,是指為了提高某個屬性(或屬性組)的查詢速度,把這個或這些索引上具有相同值的元組幾種存放在連續的物理塊。
在這里,是指索引與數據保存在同一個結構中,因此這里的聚簇,指的就是索引與數據的聚簇。
請注意,主鍵索引(或者說聚簇索引)并不是一種索引類型,而是一種將索引與數據保存在一個結構中的存儲方式。
使用 InnoDB 作為存儲引擎的表,都是基于主鍵索引來構建的,所以這就是為什么在使用 InnoDB 存儲引擎時,一定要定義一個主鍵的原因。
如果沒有定義主鍵,那么 InnoDB 會選擇一個 UNIQUE 索引類型的索引作為主鍵索引,如果沒有,InnoDB 會定義一個隱式的主鍵來作為主鍵索引。
總之,使用 InnoDB 作為存儲引擎的表,一定會有一個主鍵,如果開發者沒有定義,那么 MySQL 將會自己指定或者創建一個隱式列來作為主鍵。
這一小節的標題是主鍵構成的索引結構,其實這個標題的名字可能不太好理解,可能換成主鍵索引結構是大家普遍能接受的說法。
但是我并沒有這樣做,因為主鍵索引是一個很突兀的概念,它不是一種索引類型,也不是一種索引方法,而是一種索引與數據共存的存儲方式。
主鍵索引的優點
主鍵索引的優點,通常是與非主鍵索引進行對比得出的。最主要的一點就是在查詢數據行時,主鍵索引在大多數場景下比非主鍵索引高效。
因為如果使用了主鍵索引,那么在 B+ 樹中搜索到對應的葉子節點后,可以直接獲得該索引值對應的數據行。
而如果使用的是非主鍵索引,那么在 B+ 樹中搜索到對應的葉子節點后,還需要通過葉子節點上存儲的數據指針(或者主鍵值)再進行一次尋址(或者根據主鍵在主鍵索引中再進行一次搜索),才能得到真正的數據行。
總的來說,就是在通常情況下,主鍵索引會比非主鍵索引少一次查找數據行的過程。
主鍵索引的缺點
凡事都有兩面性,主鍵索引也不例外。
依賴順序插入
主鍵索引的插入需要依賴順序插入,否則會發生頁分裂而嚴重影響性能。在往 InnoDB 主鍵索引中插入數據時,有以下三個規則:
- 根據 B+ 樹的性質,主鍵值最終都會寫入到葉子節點中,而 InnoDB 將會把葉子節點寫入到數據頁中
- InnoDB 頁與頁之間的順序是由主鍵的大小順序決定的,新創建的頁中的主鍵值應該比之前創建頁中的主鍵值都大,且頁中每條新寫入的主鍵值應該比已經寫入的主鍵值都大
- InnoDB 默認會將新寫入的數據放在最新創建的頁中
所以結合上面的規則可以得出,InnoDB 隱式地約定了最新寫入的主鍵需要比之前寫入的所有主鍵值都要大。
那么如果新寫入的數據的主鍵值不是當前最大的主鍵值,那么 InnoDB 為了維護上面的規則,將會在之前創建的頁中尋找這個主鍵值應該存放的位置。那么如果舊頁已經滿了的情況下,將會把這一頁中的所有數據拆分為兩個頁,或者合并臨近的未滿的頁中,這種拆分-合并頁的現象就稱之為頁分裂。
在發生頁分裂時,InnoDB 將會把當前表鎖住,等頁分裂完成后,再釋放鎖,所以頁分裂是一種比較影響性能的現象。
更新代價高
當我們需要更新主鍵值時,就可能涉及到主鍵值存放位置的變動,就有可能出現頁的拆分和合并,即有可能發生頁分裂。所以主鍵的更新代價是比較高的,在日常開發中一般也會禁止更新主鍵值。
索引使用指南
在實際開發過程中,主鍵索引的用法相對固定,除主鍵索引外用的比較多的是由 BTREE 方法構建的索引,所以我們接下來的討論內容是由 BTREE 方法構建的非主鍵索引。在此章節中出現的索引,如無特殊說明,都指代由 BTREE 方法構建的非主鍵索引。
索引樹回顧
讓我們再回顧一下索引的數據存儲示意圖:
基于上面這張圖,我們來梳理一下索引的使用規則
索引樹排序規則
在介紹索引的使用規則之前,我們先了解一下索引樹的排序規則。索引樹的排序規則,主要指的是索引樹節點的關鍵字的排序規則,而在這里,關鍵字代表的就是索引字段值,那么索引樹的排序規則實際上指的就是索引字段值的排序規則。
上面是由 name、age、register_date 三個字段組成的索引樹,那么這顆索引樹的排序規則是什么樣的呢?
例如,我們現在有如下四條數據:
那么,排序的規則為:
- 首先按照 name 進行排序,由于**“小美”** = “小美” = “小美” < “美美”,則有:
- 當 name 相同時,再按照 age 進行排序,由于 22 < 48 = 48,則有:
- 當 name 和 age 都相等時,再按照 register_date 進行排序,由于 2018-07-10 < 2018-10-01,則有:
- 整合上面的所有順序,則最后的順序為:
由上面的流程我們可以得知,索引字段值的順序,正是索引字段依次比較的結果。有了這個概念,那么理解后面的索引使用規則就會容易得多。
最左前綴法則
最左前綴法則,可能是大家最耳熟能詳的索引使用規則了,規則大家也都很清楚,就是使用同一個索引的多個索引字段作為查詢條件時,必須從索引字段順序的左邊開始依次(且不能跳過字段)進行值匹配,如果從左邊開始有哪個字段沒有出現在查詢條件中,那么從這個字段開始,后面的字段查詢都不能使用索引。
最左前綴法則的產生依據
為什么會有最左前綴法則,它產生的依據是什么呢?例如,我想跳過 name 字段,直接使用 age 字段作為索引樹的查詢條件,這樣為什么不能使用索引呢?
我們知道,如果數據集有序,那么我們只要使用二分查找就能輕松把查詢性能提升幾個數量級;反之,如果在無序的數據集中進行查詢,那就只能把所有數據都遍歷一遍了。利用索引樹中的數據有序的性質,才叫真正地使用索引。
那么我們仔細觀察一下,在跳過 name 字段后,剩下兩個字段還有序嗎?很顯然,排除掉 name 字段后的剩下兩個字段值并不是有序的,即整個索引樹在排除掉 name 字段后,并不是一個有序的數據集,所以索引就不能提升查詢性能了。
最左前綴法則延申
最左前綴法則,其實不僅僅在索引字段之間有效,在同一個字段的前綴查詢中也是生效的,即字段的前綴查詢也滿足最左前綴法則,也可以使用索引。當然前提是得滿足字段間的最左前綴
例如,在本文討論的索引結構中,我們使用如下的幾個 SQL 也是滿足最左前綴法則的:
但是在如下幾個 SQL 中是不滿足最左前綴法則的,即不能(或只能使用一部分)使用索引:
-- 對于 name 進行后綴查詢,完全不能使用索引 select * from user where name like '%美';-- 對于 name 進行等值查詢,但是 age 進行后綴查詢,那么只能用到 name 的部分 select * from user where name = '小美' and age like '%2';-- 對于 name 和 age 進行等值查詢,但是 register_date 進行后綴查詢,那么只能用到 name + age 的部分 select * from user where name = '小美' and age = '22' and register_date like '%05';當然,這里的最左前綴,是建立在索引前面的字段是進行的等值查詢(=)的前提下進行討論的。索引前面的字段不是等值查詢的情況將在后面的章節中進行討論。
字段書寫順序不影響最左前綴法則
在我初學 MySQL 的時候,有人曾經問過我下面的這條 SQL 會走索引嗎?(還是以本文中的索引為例):
-- 查詢條件 select * from user where age = '22' and name = '小美' and register_date = '2015-03-05'由于當時對于 MySQL 知識的一知半解,我回答了一個不能,現在想想仍為當時的自己感到尷尬。因為當時雖然知道最左前綴匹配,但是卻不知道 MySQL 的架構中有分析器和優化器的存在,也就不知道其實查詢條件中的字段書寫順序其實并不會影響最左前綴法則。
寫到這里回憶起了這件事情,所以把它寫下來,也算是記錄一下在變強這條道路上踩過的一個腳印吧!
最左前綴法則總結
最左前綴法則,是索引最重要的使用規則之一,也是制定高性能索引策略的基礎。在日常開發過程中,我們也要遵守最左前綴法則,來盡量符合索引的使用條件。
這里需要劃重點:利用索引樹中數據有序的性質,才叫真正地使用索引。這是判斷索引能不能生效的關鍵條件,這個結論也會在后面的章節中反復用到。
索引字段匹配時不要進行操作
再使用索引字段進行匹配時,不要在索引字段上進行操作。操作包含了一切需要計算才能得到結果的動作,包括表達式計算、函數、類型轉換等。原因也很簡單,因為包含了計算,那么每條數據的索引字段值都需要計算才能得到結果,那么就只能每條數據都遍歷一遍了。
例如下面的操作是不能(或只能使用一部分)使用索引的:
要想改寫包含操作的 SQL 來實現對應的功能,有兩種做法:
- 把操作從索引字段側轉移到常量側:在第一個 SQL 中,把 -1 操作轉移到右側來,即 age = 21+1
- 建立函數索引:在第二個 SQL 中,先執行創建函數索引的語句,添加函數索引
再執行第二個 SQL,就可以使用上面創建的函數索引了。
大于小于符號范圍查詢對于索引后面字段的影響
前面我們在討論最左前綴法則時,特意提到討論的前提是建立在索引前面的字段是進行的等值查詢(=)下的。那么這一部分中將會討論建立在索引前面的字段進行范圍查詢時,對后面的索引部分的影響。
在使用索引前面的字段進行大于(>)、小于(<)符號的范圍查詢時,該字段后面的索引部分將不會被使用。
以這個葉子節點舉例,假設我有以下 SQL 語句:
-- name 進行等值查詢,age 進行大于符號的范圍查詢,register_date 使用等值查詢時,age 后面的索引部分將不會被使用 select * from user where name = '王小美' and age > 25 and register_date = '2014-04-25'-- name 進行等值查詢,age 進行小于符號的范圍查詢,register_date 使用等值查詢時,age 后面的索引部分將不會被使用 select * from user where name = '王小美' and age < 30 and register_date = '2014-04-25'上面兩個 SQL,索引并沒有使用完全,原因就是 age 字段使用了大于(>)、小于(<)符號的范圍查詢。如果將大于(>)、小于(<)符號改成 between...and... 就可以使用索引了:
-- 使用 between...and... 可以使用索引 select * from user where name = '王小美' and age between 1 and 30 and register_date = '2014-04-25'其他的規則
綜合評估索引是否使用
劃重點,MySQL 其實并不會按照死板的規則來約定走或者不走索引,而是要根據檢索比例,表數據量等因素綜合評估是否走索引,常見的需要綜合評估的查詢操作有 >、<、>=、<=、in、or。
例如下面兩個 SQL:
所以,日常開發過程中在對 SQL 進行性能分析時,不能一味地套用各種規則,而是要根據實際情況(如符合條件的數據量大小)來綜合評估索引的使用情況。
值辨別度低的字段不使用索引
在一個數據列中,如果數據值的取值范圍非常有限的情況下,重復度就會很高,這也就造成了只使用這一列作為數據的辨別條件時,數據與數據之間的辨別度很低,這樣的列就稱為值辨別度低的字段。
這樣的列在日常開發中也非常常見,例如用戶表中的性別字段,一般來說只有 2 個或者 3 個取值,那么性別字段就可以稱為值辨別度低的字段。在這樣的字段上建立索引,一般來說是得不償失的,因為在大多數情況下來說,MySQL 都不會選擇使用該字段對應的索引。
原因也很簡單,這樣的字段的篩選條件過于簡單,在表數據量大的情況下符合條件的數據量通常也會很大(例如性別字段中,男女兩種取值的數據一般各占 50%),那么與其使用索引還會帶來回表操作的開銷,不如直接全表掃描還能獲得更高的性能。
is null 或者 is not null 查詢條件也是一樣的道理,一般來說也不會使用索引。
但是這也不絕對,因為如果在某個取值的數據在表中占比很少(假設在某個系統中,女性用戶只占 1%),且這個字段符合使用索引的條件時,還是會使用索引的。
通配符開頭的模糊查詢不使用索引
當使用通配符開頭的模糊查詢時,例如 name like '%小美' 或者 name like '%小% 這樣的條件時,是不會使用索引的。原因很簡單,違反了最左前綴法則。
不等于操作不使用索引
像常見的 !=、<>、not in、not like、not exists 操作都不會使用索引。個人猜測原因主要是:
- MySQL 認為符合不等于條件的數據量比較大
- 索引樹中如果使用不等于條件,那么將無法使用索引樹有序的性質
總結
以上是生活随笔為你收集整理的MySQL 索引类别与索引使用指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小型数控雕刻机制作Arduino_开一家
- 下一篇: Docker exec 命令执行出错,