前言
該篇内容:
- 結合執行個體,介紹常見的幾種索引失效場景,以及對每個場景會做一些額外的擴充說明.
- 後面寫着寫着跑題了,為了想給大家講下什麼是回表...然後。。。。
正文
失效 一
查詢的字段列未添加索引
ps:這...直接從失效二開始看吧,我承認我在啰嗦。
請看示例,表内 userProfession 列 存入的是人員的職業資訊 :
該字段列未使用索引,是以查詢無法命中索引 :
EXPLAIN SELECT * FROM userinfo WHERE userProfession='教師';
分析結果:
失效 二
建索引的字段 類型為 varchar , 但是 查詢的 傳入值 沒有使用 ‘ ’ ,即沒加引号。
請看示例,表内 userCardNum 列 存入的是職工的工号 :
userCardNum 設定的是varchar 類型 :
給 userCardNum 添加了唯一索引 :
正常命中索引查詢SQL語句分析(使用 EXPLAIN ):
EXPLAIN SELECT * FROM userinfo WHERE userCardNum= '10011';
索引命中分析結果:
索引失效,傳入值沒有帶 ‘ ’ 單引号:
EXPLAIN SELECT * FROM userinfo WHERE userCardNum= 10011;
索引失效分析結果:
切記: 傳入的值使用方式記得跟資料庫表内列,索引設定字段保持一樣的類型,這樣萬無一失。
擴充補充,為什麼失效二情況 強調了 索引字段是 varchar ,傳入 值使用不加引号 呢?
因為一部分人在了解這種情況 有錯誤的思想,了解為 涉及類型轉換 ,以為是因為單純的字段類型不對應 導緻索引失效,這裡必須補充一下一個示例:
字段列 userAge:
userAge類型為 int :
給 userAge添加了索引 :
SQL使用傳入值,添加了引号 :
EXPLAIN SELECT * FROM userinfo WHERE userAge = '25';
結果分析 ,可以命中索引:
是以,咱們記住,這種失效場景是,資料庫表内字段是varchar,給這字段添加了索引,傳入值sql未使用引号,這時候涉及隐式轉換,才會導緻索引失效。 當然這也是為什麼我讓大家切記,保持與表内資料類型一緻,這不管是啥都很穩妥。
失效三
使用 like 進行 左模糊比對查找 ‘%XXX’
請看示例,表内字段列 userName
給userName 添加索引:
索引失效 ,使用LIKE 并使用的是左比對(同樣左右一起用也是失效的):
EXPLAIN SELECT * FROM userinfo WHERE userName LIKE '%一';
索引失效分析結果:
擴充補充,當使用LIKE ,索引就必然失效麼?
并不是,這裡強調了 左比對。
請看 LIKE 右比對:
EXPLAIN SELECT * FROM userinfo WHERE userName LIKE '王%';
索引命中分析結果:
失效 四
索引字段作為查詢條件時,使用了計算或者函數
請看示例SQL ,索引字段使用了計算:
EXPLAIN SELECT * FROM userinfo WHERE userAge +2= 37;
索引字段因使用計算,失效分析結果:
再看示例,索引字段使用函數:
EXPLAIN SELECT * FROM userinfo WHERE TRIM(userName) = '陳七';
索引字段因使用函數,失效分析結果:
失效 五
使用 OR ,條件字段中包含有未設索引字段列
請看示例,表内userProfession字段沒有添加索引, 而 userAge添加了索引:
執行SQL使用OR ,同時使用userProfession ,userAge作為條件查找:
EXPLAIN SELECT * FROM userinfo WHERE userProfession='教師' OR userAge = 25;
索引未命中分析結果:
擴充補充,
如果 使用 OR ,作為查找條件的字段都已經添加了索引 ,會是什麼情況?
例如 userAge 我們使用了索引,
EXPLAIN SELECT * FROM userinfo WHERE userAge = 23 OR userAge = 35;
這樣會命中索引麼?
注意了,分情況,因為我們的userAge添加的不是唯一索引,因為年齡嘛,總會有重複:
再看目前資料,可以看的 userAge 為 35 ,userAge為 23 的 都隻有一條資料,也就是對應目前表内資料,其實是唯一資料:
這時候,索引分析結果,索引命中了:
可以看的,索引命中了。
那麼接着,我們把一個userAge查找條件換成25, 資料表内資料 25 的有兩條,不唯一:
EXPLAIN SELECT * FROM userinfo WHERE userAge = 25 OR userAge = 35;
這時候,索引分析結果,索引失效了:
ps: 也就是,若把索引設定成為唯一索引,那麼資料庫内也就不存在重複資料了,這時候如果使用or 查詢同個索引字段列,那麼就是命中的。當時往往有時候,就是類似文中的情況。userAge就是設定為不唯一索引,那麼就是使用 UNION ALL 去解決或者從代碼層面分開查詢。
配上使用 UNION ALL的方式:
EXPLAIN
SELECT * FROM userinfo WHERE userAge = 25
UNION ALL
SELECT * FROM userinfo WHERE userAge = 35
索引分析結果,是命中的:
失效 六
聯合索引,不滿足最佳左字首原則,導緻索引失效
請看示例,表内 userWeight 、userHeight、userSight 三個字段:
給 userWeight 、userHeight、userSight 這三個字段建立聯合索引 :
注意順序,最左為 userWeight
這時候,如果SQL 在使用這些字段索引查找時,先看單個字段查找:
單個使用userWeight :
EXPLAIN SELECT * FROM userinfo WHERE userWeight='50';
索引分析結果,索引命中,因為滿足了最佳做字首原則:
那麼我們換成單個使用 userHeight,
EXPLAIN SELECT * FROM userinfo WHERE userHeight='180' ;
索引分析結果,索引失效了:
同樣換成 userSight 也是一樣,索引失效了,因為這種情形就是沒有滿足最佳做字首原則。
對于使用聯合索引,還沒完。
當我們同時使用聯合索引裡面的2個或2個以上的字段列時:
使用 userWeight 和 userSight:
EXPLAIN SELECT * FROM userinfo WHERE userWeight='50' and userSight='5.2';
索引分析結果,索引命中(因為 userWeight=‘50’ ,滿足了最佳左字首原則):
那麼如果我們把這兩個條件 換下前後順序呢?
EXPLAIN SELECT * FROM userinfo WHERE userSight='5.2' AND userWeight='50';
索引分析結果,索引命中:
這是特意補充提一下的,這個最佳左字首原則是對于建立的聯合索引裡面字段的順序最左而言,不是sql語句寫的條件順序。
那麼如果我們使用的是 userHeight 和 userSight 呢?
EXPLAIN SELECT * FROM userinfo WHERE userHeight='180' and userSight='5.2';
這個不做解釋,連最左的邊都沒粘上,還想命中索引? 怎麼敢的。
失效 七
使用了 is not null 、 is null ,索引不生效
前排說明,這個失效場景并不是如表述所言!情況以下分析!
請看示例表資料,裡面userName裡面,包含了一條資料為NULL的情況:
然後SQL使用了 is not null :
EXPLAIN SELECT * FROM userinfo WHERE userName is not null;
索引分析結果,索引未命中:
那麼SQL使用了 is null:
EXPLAIN SELECT * FROM userinfo WHERE userName is null;
索引分析結果,索引命中:
為什麼,為什麼索引命中了? 不是說使用了 is null 、is not null 會失效麼?
這段話摘自mysql官方文檔,is null 不會影響索引的使用:
MySQL can perform the same optimization on col_name IS NULL that it can use for col_name = constant_value. For example, MySQL can use indexes and ranges to search for NULL with IS NULL. |
那可能有很多看官,确實遇到了使用is null 失效的問題,就會很疑惑。
是玄學麼? 并不是。
這就是本文想補充的一種索引失效的場景:
當執行的sql語句,mysql認為掃描全表都比使用索引快的時候,那麼索引就不會被使用!
也就是mysql會去計算查詢成本,那個成本低就選擇哪種方式。
PS: 接下來我跑題了, 非常啰嗦,我跑出了 回表,聚集索引 ,非 聚集索引,索引覆寫 這些不符合文章主題的内容。不想閱讀的看官可以點個贊就走了。
ok,繼續回到跑題,那啥時候mysql才會有這種認為呢?
通常mysql有這種想法的時候,大多數情況是因為該sql 查詢中回表數量太多。
那麼引申出一個概念,回表。
什麼是回表(跑題,但是無所謂了)?
想深入了解的看官,可以去摸索深入一下,這裡我用我的小白文給大家簡單講一講。
結合執行個體講解(對了,該篇文章都是基于引擎InnoDB的):
假如咱們現在有一張表,裡面有 id (主鍵),userName(索引),userPorfession (無索引)
首先為了更好地解釋回表, 我逼不得已又得引出兩個 新概念 聚集索引 和 非 聚集索引 。
(哈哈麻煩了,感覺跑題越跑越遠了,無所謂了)
聚集索引 :
通常來說,就是咱們表的主鍵。
那如果這張表沒有主鍵,那麼第一個建立的唯一非空索引,就是該表的聚集索引 。
那麼你說,又沒主鍵,又沒建立唯一非空索引,我明白,你想搞事。 但是mysql不會被你搞,這種情況mysql會隐式地為該表建立一個聚集索引 ,具體是啥,建立規則,我就不細說了。(因為對于我來說,我不想看到這種情況出現,就算沒必要很多時候我也會去建一個主鍵id作為僞列。)
好了,聚集索引 我們已經了解它的由來。
那麼它的作用:
簡單一點來說,它就是這表資料的老大,隻要命中這個家夥,其他字段它都能給你找齊,也就是說這個家夥是指向了整行資料的。
非聚集索引 :
其他索引,類似這個表裡,userName的索引 就是 非聚集索引 。
這個家夥的作用:
你找它,它隻能幫你找到它的老大 聚集索引(主鍵),也就是這個家夥是指向聚集索引的。
ok,講到這裡,回到我們的表示例,講講啥是回表場景:
id (主鍵),userName(索引),userPorfession (無索引)
執行SQL:
SELECT id , userName, userProfession FROM userinfo WHERE id = 7;
執行計劃分析:
這時候,索引命中的是id 主鍵 。
沒錯,命中了老大, 不會回表。因為在老大的索引樹裡面,啥玩意都能給你找齊了。
再看執行SQL:
SELECT id , userName, userProfession FROM userinfo WHERE userName = '劉二';
執行計劃分析:
這時候,命中的不是老大,是一個 非聚集索引 , 這時候需要回表。
為什麼啊? 因為我們使用的是select * ,意思是我們還得查找 userPorfession 字段資料。
那麼現在命中了userName 的索引 nameIndex,它還能幫我們找到老大 id,但是它無法幫我們直接找到 userPorfession 。
其實這裡涉及到一個概念,叫 索引覆寫 。
什麼是索引覆寫? (不能再跑題了,這裡我就提一句吧,就是從索引樹裡面指向的資料字段裡已經包含了select xx,xxx 這些字段,那麼就是索引資料已經夠用了,沒必要回表查額外的資料了。)
快速看圖了解:
執行分析結果 :
回歸剛剛講到的,userName ,id 我們都能找到,但是為了找 userPorfession ,我們隻能在找到id之後,再根據id再去找一遍主鍵的索引樹資料,找出與id綁定的userPorfession,這種情形就是 回表
什麼叫回表?好的這裡簡單講述完畢。
那又又又回到我們最早提到的問題,
mysql認為掃描全表都比使用索引快的時候,那麼索引就不會被使用。
而通常mysql有這種想法的時候,大多數情況是因為該sql 查詢中回表數量太多。
那麼怎麼盡可能避免這種情形呢?
剛剛已經講了回表是啥原因導緻的了,那麼為了盡可能避免這種情形,那就是:
1.使用聚集索引 也就是主鍵進行查找
2. select 查找的字段列 被 命中的索引的索引樹裡的資料 包含,也就是索引覆寫。
3.更新索引
1.2都看懂了。3是什麼意思呢?
就是說針對某些場景,例如目前SQL:
SELECT id , userName, userProfession FROM userName = '劉二';
因為目前id有主鍵索引,userName有索引,但是 userProfession沒索引導緻需要回表。
那麼我們把userName的單索引更新為 聯合索引 (userName,userProfession)。