當然請記住,explain是一個好習慣!
MySQL索引失效的常見場景
在驗證下面的場景時,請准備足夠多的數據量,因為數據量少時,MySQL的優化器有時會判定全表掃描無傷大雅,就不會命中索引了。
1. where語句中包含or時,可能會導致索引失效
使用or並不是一定會使索引失效,你需要看or左右兩邊的查詢列是否命中相同的索引。
假設USER表中的user_id列有索引,age列沒有索引。
下面這條語句其實是命中索引的(據說是新版本的MySQL才可以,如果你使用的是老版本的MySQL,可以使用explain驗證下)。
select * from `user` where user_id = 1 or user_id = 2
但是這條語句是無法命中索引的。
select * from `user` where user_id = 1 or age = 20;
假設age列也有索引的話,依然是無法命中索引的。
select * from `user` where user_id = 1 or age = 20;
因此才有建議說,盡量避免使用or語句,可以根據情況盡量使用union all或者in來代替,這兩個語句的執行效率也比or好些。
2. where語句中索引列使用了負向查詢,可能會導致索引失效
負向查詢包括:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等。
某“軍規”中說,使用負向查詢一定會索引失效,筆者查了些文章,有網友對這點進行了反駁並舉證。
其實負向查詢並不絕對會索引失效,這要看MySQL優化器的判斷,全表掃描或者走索引哪個成本低了。
3. 索引字段可以為null,使用is null或is not null時,可能會導致索引失效
其實單個索引字段,使用is null或is not null時,是可以命中索引的,但網友在舉證時說兩個不同索引字段用or連接時,索引就失效了,筆者認為確實索引失效,但這個鍋應該由or來背,屬於第一種場景~~
假設USER表中的user_id列有索引且允許null,age列有索引且允許null。
select * from `user` where user_id is not null or age is not null;
不過某些“軍規”和規范中都有強調,字段要設為not null並提供默認值,是有原因值得參考的。
- null的列使索引/索引統計/值比較都更加復雜,對MySQL來說更難優化。
- null 這種類型MySQL內部需要進行特殊處理,增加數據庫處理記錄的復雜性;同等條件下,表中有較多空字段的時候,數據庫的處理性能會降低很多。
- null值需要更多的存儲空,無論是表還是索引中每行中的null的列都需要額外的空間來標識。
- 對null 的處理時候,只能采用is null或is not null,而不能采用=、in、<、<>、!=、not in這些操作符號。如:where name!='shenjian',如果存在name為null值的記錄,查詢結果就不會包含name為null值的記錄。
4. 在索引列上使用內置函數,一定會導致索引失效
比如下面語句中索引列login_time上使用了函數,會索引失效:
select * from `user` where DATE_ADD(login_time, INTERVAL 1 DAY) = 7;
4.1 隱式類型轉換導致的索引失效
比如下面語句中索引列user_id為varchar類型,不會命中索引
select * from `user` where user_id = 12;
這是因為MySQL做了隱式類型轉換,調用函數將user_id做了轉換。
select * from `user` where CAST(user_id AS signed int) = 12;
4.2 隱式字符編碼轉換導致的索引失效
當兩個表之間做關聯查詢時,如果兩個表中關聯的字段字符編碼不一致的話,MySQL可能會調用CONVERT函數,將不同的字符編碼進行隱式轉換從而達到統一。作用到關聯的字段時,就會導致索引失效。
比如下面這個語句,其中d.tradeid字符編碼為utf8,而l.tradeid的字符編碼為utf8mb4。因為utf8mb4是utf8的超集,所以MySQL在做轉換時會用CONVERT將utf8轉為utf8mb4。簡單來看就是CONVERT作用到了d.tradeid上,因此索引失效。
select l.operator from tradelog l , trade_detail d where d.tradeid=l.tradeid and d.id=4;
5. 對索引列進行運算,一定會導致索引失效
運算如+,-,*,/等,如下:
select * from `user` where age - 1 = 10;
優化的話,要把運算放在值上,或者在應用程序中直接算好,比如:
select * from `user` where age = 10 - 1;
6. like通配符可能會導致索引失效
like查詢以%開頭時,會導致索引失效。解決辦法有兩種:
將%移到后面,如:
select * from `user` where `name` like '李%';
利用覆蓋索引來命中索引。
select name from `user` where `name` like '%李%';
7. 聯合索引中,where中索引列違背最左匹配原則,一定會導致索引失效
當創建一個聯合索引的時候,如(k1,k2,k3),相當於創建了(k1)、(k1,k2)和(k1,k2,k3)三個索引,這就是最左匹配原則。
比如下面的語句就不會命中索引:
select * from t where k2=2; select * from t where k3=3; slect * from t where k2=2 and k3=3;
下面的語句只會命中索引(k1):
slect * from t where k1=1 and k3=3;
8. MySQL優化器的最終選擇,不走索引
上面有提到,即使完全符合索引生效的場景,考慮到實際數據量等原因,最終是否使用索引還要看MySQL優化器的判斷。當然你也可以在sql語句中寫明強制走某個索引。
優化索引的一些建議:
- 禁止在更新十分頻繁、區分度不高的屬性上建立索引。
- 更新會變更B+樹,更新頻繁的字段建立索引會大大降低數據庫性能。
- “性別”這種區分度不大的屬性,建立索引是沒有什么意義的,不能有效過濾數據,性能與全表掃描類似。
- 建立組合索引,必須把區分度高的字段放在前面。