MySQL索引失效分析 總結:

爲了演示索引失效的問題,首先來建一張表:

create table staffs(
   id int primary key auto_increment,
   name varchar(100) not null default '' comment '姓名',
   age int not null default 0 comment '年齡',
   pos varchar(100) not null default '' comment '職位',
   add_time timestamp not null default current_timestamp comment '入職時間'
) charset utf8 comment '員工表';

insert into staffs(name, age, pos, add_time) values('張三', 22, 'manager', now());
insert into staffs(name, age, pos, add_time) values('july', 23, 'dev', now());
insert into staffs(name, age, pos, add_time) values('tom', 23, 'dev', now());
insert into staffs(name, age, pos, add_time) values('2000', 23, 'dev', now());

alter table staffs add index idx_staffs_nameAgePos(name, age, pos);

索引問題大概可以分爲以下十種情況:

  • 最好的情況就是全值匹配
  • 最佳左前綴法則
  • 不在索引列上做任何操作(計算、函數、類型轉換),這些操作會導致索引失效
  • 存儲引擎不能使用索引中範圍條件右邊的列
  • 儘量使用覆蓋索引(查詢列和索引列一致),避免select *
  • MySQL中使用不等於(!= 或者 <>)的時候會導致索引失效
  • is nullis not null也無法使用索引
  • like以通配符開頭('%abc')會導致索引失效
  • 字符串不加單引號索引會失效
  • 少用or,用它來連接時索引會失效

下面就詳細說說這十種情況。

1. 全值匹配:

staffs表建表時我們建立了一個聯合索引,如下:

可以看到,一樓是name,二樓是age,三樓是pos。

依次查看下面語句的執行計劃:**

explain select  * from staffs where name = 'july';
explain select  * from staffs where name = 'july' and age = 23;
explain select  * from staffs where name = 'july' and age = 23 and pos = 'dev';

執行結果如下:

第三種情況,就是全值匹配。即我們建立的索引一樓是name,二樓是age,三樓是pos,查詢的條件也是先name再age最後pos,從上面圖中第三條語句的執行計劃可以看出,使用三個const,效率是很高的。

2. 最佳左前綴法則:

再執行下面的語句,看結果:

explain select  * from staffs where age = 23 and pos = 'dev';
explain select  * from staffs where pos = 'dev';
explain select  * from staffs where name = 'july';
explain select  * from staffs where name = 'july' and pos = 'dev';

可以發現,第一第二條語句的索引是失效了,而第三和第四條是用到了索引的。第一第二條沒有用到name,即把一樓樓梯拆了,所以二樓三樓也就用不到了;第三條用到了一樓,所以沒問題;第四條用到了一樓和三樓,但是中間的二樓沒了,不能直接跳到三樓,所以也只能用到一樓,會部分失效。這就是最佳左前綴法則,即一樓一定不能少,帶頭大哥不能死,否則就會導致索引全部失效,中間兄弟不能斷,否則會導致索引部分失效。

那麼如果是這樣的語句能不能用到索引呢?

explain select  * from staffs where pos= 'dev' and age = 23 and name= 'july';

看結果:

可以看到,三個索引都用到了。我們建立的索引是name,age,pos,用的時候反過來了,但是這個並不影響,帶頭大哥沒死,中間兄弟沒斷,經過MySQL的優化器,就會自動進行調整,以達到最優。

如果是這樣呢?

explain select  * from staffs where name = 'july' and pos > 'dev' and age = 23;

可以看到,key len還是608,說明還是三個都用到了。爲什麼?不是說範圍之後全失效嗎?別忘了,優化器會把age條件放到前面去,pos條件放到最後,所以三個都可以用上。

3. 不在索引列做任何操作:

執行下面的語句:

explain select * from staffs where name = 'july';
explain select * from staffs where left(name,4) = 'july';

第一條語句是用name查詢,第二條語句是在name列上包了一個函數,即查詢name列左邊四位等於july的記錄。查看執行計劃如下:

可以看到,第一句是用到了索引的,但是第二句沒有,因爲第二句中索引列使用了函數。所以索引列上少計算

4. 存儲引擎不能使用索引中範圍條件右邊的列:

這個是啥意思?請看案例,執行下面兩條sql:

explain select  * from staffs where name = 'july' and age = 23 and pos = 'dev';
explain select  * from staffs where name = 'july' and age > 23 and pos = 'dev';

第一條語句毫無疑問,全值匹配,最佳情況。第二條,帶頭大哥沒死,中間兄弟沒斷,索引列上沒計算,但是age不是常量,給的是一個範圍,結果執行計劃看到的是range。這種情況,name索引用到了,age也用到了,但不是精確檢索,而是一個範圍,最後的pos就沒有用到,所以結論就是範圍之後全失效

5. 儘量使用覆蓋索引(查詢列和索引列一致),避免select *

查看以下兩句的執行計劃:**

explain select  * from staffs where name = 'july' and age = 23 and pos = 'dev';
explain select  name, age, pos from staffs where name = 'july' and age = 23 and pos = 'dev';

可以看到,如果查詢字段和索引列完全一致,或者在索引列的範圍內,比如select name, age,那麼extra中是有using index的,這個效率是高於select *的。

6. MySQL中使用不等於(!= 或者 <>)的時候會導致索引失效:

查看下面語句的執行計劃:

explain select  * from staffs where name != 'july' and age = 23 and pos = 'dev';
explain select  * from staffs where name <> 'july' and age = 23 and pos = 'dev';

可以看到,使用了!=或者<>確實導致索引失效了。

7. is null,is not null也無法使用索引:

查看下面語句的執行計劃:

explain select  * from staffs where name is null and age = 23 and pos = 'dev';
explain select  * from staffs where name is not null and age = 23 and pos = 'dev';

is null的情況是最糟糕的,所以我們數據列如果經常用來當查詢條件的話,最好設置默認值,而不能讓它爲null。

8. like以通配符開頭('%abc')會導致索引失效:

查看如下語句的執行計劃:

explain select  * from staffs where name like '%july';
explain select  * from staffs where name like 'july%';
explain select  * from staffs where name like '%july%';

結果如下:

根據結果可以發現,只要左邊出現了百分號,那麼索引就失效了。所以百分like加右邊。但是有些情況必須得百分號寫左邊,那麼怎麼解決索引失效的問題呢?一般我們會採用覆蓋索引來解決。比如上面這種情況,不要select *,像下面這樣就行了:

explain select  name from staffs where name like '%july%';
explain select  age from staffs where name like '%july%';
explain select  age,pos from staffs where name like '%july%';
explain select  name,age,pos from staffs where name like '%july%';

可以看到,全部都是用到了索引的。

like也是表示範圍,但是如果是百分號寫右邊,這種範圍和大於小於是不一樣的,百分號寫右邊的like,後面的字段索引也是不會失效的。

9. 字符串不加單引號索引會失效:

查看如下語句執行計劃:

explain select  * from staffs where name = '2000';
explain select  * from staffs where name = 2000;

可以發現,沒加單引號,就會導致索引失效的。varchar類型的,沒加單引號,存在類型轉換,從而索引失效。

10. 少用or,用它來連接時索引會失效:

查看下面語句的執行計劃:

explain select  * from staffs where name = '2000' or name = 'july';

可以看到,用了or以後,索引失效了。

總結:

全值匹配我最愛, 最左前綴要遵守;
帶頭大哥不能死, 中間兄弟不能斷;
索引列上少計算, 範圍之後全失效;
模糊百分寫最右, 覆蓋索引不寫星;
不等空值還有或, 索引失效要少用;
字符引號不可丟, 牢記以上就無憂。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章