規避MySQL中的索引失效

前言

以前有看過許多相似的文章內容,提到過一些sql語句的使用不當會致使MySQL的索引失效。還有一些MySQL「軍規」或者規範寫明瞭某些sql不能這麼寫,不然索引失效。程序員

絕大部分的內容筆者是承認的,不過部分舉例中筆者認爲用詞太絕對了,並無說明其中的起因,不少人不知道爲何。因此筆者絕對再整理一遍MySQL中索引失效的常見場景,並分析其中的起因供你們參考。sql

固然請記住,explain是一個好習慣!數據庫

MySQL索引失效的常見場景

在驗證下面的場景時,請準備足夠多的數據量,由於數據量少時,MySQL的優化器有時會斷定全表掃描無傷大雅,就不會命中索引了。後端

1. where語句中包含or時,可能會致使索引失效

使用or並非必定會使索引失效,你須要看or左右兩邊的查詢列是否命中相同的索引。bash

假設USER表中的user_id列有索引,age列沒有索引。markdown

下面這條語句實際上是命中索引的(聽說是新版本的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列也有索引的話,依然是沒法命中索引的。oop

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;
複製代碼

這種狀況通常有兩種解決方案。

方案1: 將關聯字段的字符編碼統一。

方案2: 實在沒法統一字符編碼時,手動將CONVERT函數做用到關聯時=的右側,起到字符編碼統一的目的,這裏是強制將utf8mb4轉爲utf8,固然從超集向子集轉換是有數據截斷風險的。以下:

select d.* from tradelog l , trade_detail d where d.tradeid=CONVERT(l.tradeid USING utf8) and l.id=2; 
複製代碼

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語句中寫明強制走某個索引。

優化索引的一些建議

  1. 禁止在更新十分頻繁、區分度不高的屬性上創建索引。
  • 更新會變動B+樹,更新頻繁的字段創建索引會大大下降數據庫性能。
  • 「性別」這種區分度不大的屬性,創建索引是沒有什麼意義的,不能有效過濾數據,性能與全表掃描相似。
  1. 創建組合索引,必須把區分度高的字段放在前面。

參考

《爲何這些SQL語句邏輯相同,性能卻差別巨大?》

《後端程序員必備:索引失效的十大雜症》

《58到家數據庫30條軍規解讀》

《MySQL的or/in/union與索引優化 | 架構師之路》

相關文章
相關標籤/搜索