mysql索引失效的常見原因和如何用好索引


本篇主要介紹的是索引失效的常見原因和如何用好索引,跟上一篇正好承上啟下,給有需要的朋友一個參考。

本文將從以下幾個方便進行講解:

1.索引失效常見原因:

 

2.索引失效常見誤區:

 

3.索引設計的幾個建議:

 

准備工作

查看當前 mysql 的版本:

select VERSION(); 

查出當前版本為:8.0.21

創建一張表 test1

CREATE TABLE `test1` ( `id` bigint NOT NULL, `code` varchar(30) NOT NULL, `age` int NOT NULL, `name` varchar(30) NOT NULL, `height` int NOT NULL, PRIMARY KEY (`id`), KEY `idx_code_age_name` (`code`,`age`,`name`) USING BTREE, KEY `idx_height` (`height`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 

插入兩條數據:

INSERT INTO `test1`(`id`, `code`, `name`, `age`,`address`) VALUES (1, '001', '張飛', 18,'7'); INSERT INTO `test1`(`id`, `code`, `name`, `age`,`address`) VALUES (2, '002', '關羽', 19,'8'); 

查詢一下:

select * from test1; 

結果:

 

此外建立了兩個索引:idx_code_age_name( code , age , name )聯合索引 和 idx_height(height )普通索引。

 

一. 索引失效常見原因

1.不滿足最左前綴原則

第1種情況:

where 條件后的字段包含了聯合索引的所有索引字段,並且順序是按照: code 、 age 、name 。

執行sql如下:

explain select * from test1 where code='001' and age=18 and name='張飛' ; 

結果:

從圖中標紅的地方可以看出已經走了聯合索引idx_code_name_age,並且索引的長度是 188, 188 = 30 * 3 + 2 + 30 * 3 + 2 + 4 ,索引是使用充分的,索引使用效率最佳。

有些朋友可能會問:索引長度為什么是這樣計算的?

答:請參考《 explain | 索引優化的這把絕世好劍,你真的會用嗎? 》,里面給出了非常詳細的講解。

第2種情況:

where 條件后的字段包含了聯合索引的所有索引字段,順序是不按照: code 、 age 、name。

執行sql如下:

explain select * from test1 where code='001' and name='張飛' and age=18; 

結果:

從上圖中看出執行結果跟第一種情況一樣。

注意:這種情況比較特殊,在查詢之前mysql會自動優化順序。

第3種情況:

where 條件后的字段包含了聯合索引中的: code 字段

執行sql如下:

explain select * from test1 where code='001'; 

結果:

從上圖看出也走了索引,但是索引長度有所變化,現在變成了 92 , 92 = 30*3 + 2 ,只用到了一個索引字段code,索引使用不充分。

第4種情況:

where 條件后的字段包含了聯合索引中的: age 字段

執行sql如下:

explain select * from test1 where age=18; 

結果:

從上圖中看變成了全表掃描,所有的索引都失效了。

第5種情況:

where 條件后的字段包含了聯合索引中的: name 字段

執行sql如下:

explain select * from test1 where name='張飛'; 

結果:從上圖中看變成了全表掃描,所有的索引都失效了。

第6種情況:

where 條件后的字段包含了聯合索引中的: code 和 age 字段

執行sql如下:

explain select * from test1 where code='001' and age=18; 

結果:

從上圖中看出還是走了索引,但是索引長度變成了: 96 , 96 = 30*3 + 2 + 4 ,只用到了兩個索引字段code和age,索引使用也不充分。

第7種情況:

where 條件后的字段包含了聯合索引中的: code 和 name 字段

執行sql如下:

explain select * from test1 where code='001' and name='張飛'; 

結果:

從上圖中看出走的索引長度跟第1種情況一樣,長度也是 92 。也就是說只用到了一個索引字段 code ,而 age 字段的索引失效了。

第8種情況:

where 條件后的字段包含了聯合索引中的: age 和 name 字段

執行sql如下:

explain select * from test1 where age=18 and name='張飛'; 

結果:

從上圖中看出變成了全表掃描,所有的索引都失效了。

小結:

  1. code 
  2. code 
  3. 如果中間出現斷層,如: code、name ,只會走第一個索引code,從斷層后的索引都會失效。
  4.  name name

2.范圍索引列沒有放在最后

where 條件后的字段 age 用了大於等於,具體sql如下:

EXPLAIN select * from test1 where code='001' and age>18 and name='張飛' ; 

結果:

從上圖中看出索引長度變成: 96 , 96 = 30*3 + 2 + 4 ,只用到了兩個索引字段 code 和age ,而 name 字段的索引失效了。

如果范圍查詢的語句放到最后:

EXPLAIN select * from test1 where code='001' and name='張飛' and age>18 ; 

結果:

什么鬼?怎么索引長度還是: 96 ?

這是一個非常經典的錯誤

范圍查詢放最后是指創建聯合索引的字段順序,現在的順序是:

 

調整一下把索引字段name和age的順序調整一下:

 

再執行上面的sql,結果:

從上圖中看出索引長度變成: 188 ,索引使用充分了。

回過頭再執行剛開始的那條sql:

EXPLAIN select * from test1 where code='001' and age>18 and name='張飛'; 

結果:

什么?

索引長度也是: 188 。

注意:范圍查詢放最后,指的是聯合索引中的范圍列放在最后,不是指where條件中的范圍列放最后。如果聯合索引中的范圍列放在最后了,即使where條件中的范圍列沒放最后也能正常走到索引。

3.使用了select *

其實在《阿里巴巴開發手冊》中也明確說了,禁止使用select * ,這是為什么呢?

EXPLAIN select * from test1 

結果:

從上圖中看出走了全表掃描。

那么如果查詢的是索引列:

EXPLAIN select code,age,name from test1 

結果:

從圖中可以看出這種情況走了全索引掃描,比全表掃描效率更高。

其實這里用到了: 覆蓋索引 。

如果 select 的列都是索引列,則被稱為 覆蓋索引 。

如果 select 的列不只包含索引列,則需要 回表 ,即回到表中再查詢出其他列,效率相當更低一些。 select * 大概率需要查詢非索引列,需要 回表 ,因此要少用。

當然,本文中很多示例都使用了 select * ,主要是我表中只有兩條數據,為了方便演示,正常業務代碼中是要杜絕這種寫法的。

4.索引列上有計算

執行sql如下:

explain select * from test1 where height+1 =7; 

結果:從上圖中可以看出變成全表掃描了,由此可見在索引列上有計算,索引會失效。

5.索引列上使用了函數

如果在索引列加某個函數,具體sql如下:

explain select * from test1 where SUBSTR(height,1,1)=8; 

結果:從上圖中可以看出變成全表掃描了,由此可見在索引列上加了函數,索引也會失效。

6.字符類型沒加引號

廢話不多說直接上sql:

explain select * from test1 where name = 123; 

結果:

從圖中看出走的全表掃描,索引失效了。

為什么索引會失效呢?

這里有些朋友可能會有點懵。

答: name 字段是 字符類型 ,而等於號右邊的是 數字類型 ,類型不匹配導致索引丟失。

所以在使用字符類型字段做判斷時,一定要加上單引號。

類型不匹配導致索引丟失問題,是我們平時工作中非常容易忽視的問題,一定要引起足夠的重視

7.用is null和is not null沒注意字段是否允許為空

前面創建的test1表中height字段是非空的。

 

查詢sql如下:

explain select * from test1 where height is null; 
explain select * from test1 where height is not null; 

結果都是:從上圖中看出都是全表掃描,索引都失效了。

如果height字段改成允許為空的呢?

 

上面第一條sql執行結果:

從上圖中看出走了 ref 類型的索引。

上面第二條sql執行結果:

從上圖中看出走了 range 類型的索引。

小結

  1. 如果字段不允許為空,則is null 和 is not null這兩種情況索引都會失效。
  2. 如果字段允許為空,則is null走 ref 類型的索引,而is not null走 range 類型的索引。

8.like查詢左邊有%

like查詢主要有三種情況:

  • like '%a'
  • like 'a%'
  • like '%a%'

先看看第一種情況:

explain select * from test1 where code like '%001'; 

結果:

從上圖看出走的全表掃描,索引失效了。

再看看第二種情況:

explain select * from test1 where code like '001%'; 

結果:

從上圖看出走的 range 類型的索引。

最后看看第三種情況:

explain select * from test1 where code like '%001%'; 

結果:從上圖看出走的全表掃描,索引也失效了。

從這三種結果看出 like 語句只有 % 在右邊才能走索引。

如果有些場景就是要使用 like 語句 % 在左邊該怎么辦呢?

答案:使用覆蓋索引

具體sql如下:

explain select code,age,name from test1 where code like '%001%'; 

結果:

從上圖看出走的 index 類型的全索引掃描,相對於全表掃描性能更好。

當然,最佳實踐是在 sql 中要避免 like 語句 % 在左邊的情況,如果有這種業務場景可以使用es 代替 mysql 存儲數據。

小結:

  • like '%a' 索引失效
  • like 'a%' 走range類型索引
  • like '%a%' 索引失效

9.使用or關鍵字時沒有注意

用法如下:
explain select * from test1 where height = 8 or height = 9; 

結果:

從上圖中看出走了 range 類型的索引,不是沒問題嗎?

再把sql改一下:

explain select * from test1 where code = '001' or height = 8; 

結果:

從上圖中可以看出變成了全表掃描,索引失效了。

我們不妨單獨查詢一下:

explain select * from test1 where code = '001'; 

結果:

explain select * from test1 where height = 8; 

結果:

兩種單獨查詢的情況都走了 ref 類型的索引,但是使用 or 關鍵字后sql的索引會失效。

那么,我們在想使用 or 的場景,又想讓索引有效,該怎么辦呢?

explain (select * from test1 where code = '001') union (select * from test1 where height = 8); 

沒錯,使用 union 關鍵字,但是跟 or 關鍵字的語法稍微有點區別,不過查詢的數據結果是一樣的。

上面sql執行結果如下:

我們看到走了 ref 類型索引。

or關鍵字會讓索引失效,可以用union代替

二. 索引失效的常見誤區

1.使用not in會導致索引失效

用法如下:

explain select * from test1 where height not in (7,8); 

結果:

從上圖中看出是走了 range 類型索引的,並沒失效。

需要特別說明的是mysql5.7和5.8不同的版本效果不一樣,5.7中這種情況sql執行結果是全表掃描,而5.8中使用了 range 類型索引。

2.使用不等於號會導致索引失效

用法如下:

explain select * from test1 where height!=8; 

結果:

從圖中看出走了 range 類型的索引。

需要特別說明的是mysql5.7和5.8不同的版本效果不一樣,5.7中這種情況sql執行結果是全表掃描,而5.8中使用了 range 類型索引。5.7中如果想使用索引該怎么辦呢?答案:使用大於和小於代替不等於。

在這里溫馨的提醒一聲,不等於號不只是 != ,還包括 <> 。

3.order by索引字段順序不當導致索引失效

sql中除了 where 后面的字段能走索引之外, order by 后面的字段也能走索引。

EXPLAIN select * from test1 where code='001' order by age,name; 

結果:

從上圖中看出走了 ref 類型的索引,索引長度是 92 ,並且沒有額外信息。

但是如果把 order by 后面的條件改成如下兩種排序:

EXPLAIN select * from test1 where code='001' order by name; 
EXPLAIN select * from test1 where code='001' order by name,age; 

結果:

從上圖中看出還是走了 ref 類型的索引,索引長度是 92 ,但是額外信息中提示: Using filesort ,即按文件重排序。

上面兩個例子能夠看出有沒有使用索引跟 where 后面的條件有關,而跟 order by 后面的字段沒關系。

而需不需要按文件重排序,則跟 order by 后面的字段有直接關系。

問題來了,額外信息中提示: Using filesort 這種該如何優化?

答:這種情況一般是聯合索引中索引字段的順序,跟 sql 中 where 條件及 order by 不一致導致的,只要順序調整一致就不會出現這個問題。

三. 索引設計的幾個建議:

  1. 優先使用唯一索引,能夠快速定位
  2. 為常用查詢字段建索引
  3. 為排序、分組和聯合查詢字段建索引
  4. 一張表的索引數量不超過5個
  5. 表數據量少,可以不用建索引
  6. 盡量使用占用空間小的字段建索引
  7. 用idx_或unx_等前綴命名索引,方面查找
  8. 刪除沒用的索引,因為它會占一定空間

四. 彩蛋:

特別說明:索引失效除了上述的常見問題之外, mysql 通過索引掃描的行記錄數超過全表的10%~30% 左右,優化器也可能不會走索引,自動變成全表掃描。

送給大家一個避坑口訣:

  • 全職匹配我最愛,最左前綴要遵守
  • 帶頭大哥不能死,中間兄弟不能斷
  • 索引列上少計算,范圍列后全失效
  • like百分寫最右,覆蓋索引不寫*
  • 不等空值還有or , 索引影響要注意;
  • 字符字段引號不能丟,sql優化有訣竅。

 

轉載自:https://my.oschina.net/u/4815822/blog/4818355


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM