你們都知道,mysql 一個表中能夠建立多個索引,可是在執行一條查詢語句的時候,mysql 只能選一個索引,若是咱們沒有指定 mysql 使用某個索引,那麼就是由 mysql 的優化器來決定要使用哪一個索引了,然而,mysql 也是會有選錯的時候。mysql
前面的文章,咱們有介紹過執行一條查詢 sql 語句分別會經歷那些過程,執行一條sql語句都經歷了什麼? 存在多個索引的狀況下,優化器通常會經過比較掃描行數、是否須要臨時表以及是否須要排序等,來做爲選擇索引的判斷依據。sql
咱們先來新建一個表,建立兩個普通索引。bash
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a` (`a`),
KEY `b` (`b`)
) ENGINE=InnoDB;
複製代碼
這裏咱們使用存儲過程往表裏插入 10w 測試數據,若是對 mysql 的存儲過程不熟悉,請看我在代碼中的註釋,應該能看得懂得。post
#定義分割符號,mysql 默認分割符爲分號;,這裏定義爲 //
#分隔符的做用主要是告訴mysql遇到下一個 // 符號即執行上面這一整段sql語句
delimiter //
#建立一個存儲過程,並命名爲 testData
create procedure testData()
#下面這段就是表示循環往表裏插入10w條數據
begin
declare i int;
set i=1;
while(i<=100000)do
insert into t values(i, i, i);
set i=i+1;
end while;
end // #這裏遇到//符號,即執行上面一整段sql語句
delimiter ; #恢復mysql分隔符爲;
call testData(); #調用存儲過程
複製代碼
數據插入完成後,咱們來看下面這條 sql 語句。測試
select * from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1;
複製代碼
因爲主鍵id、a、b 三個字段的值其實都是同樣的,因此其實這條 sql 語句的結果集爲空,沒有符合條件的記錄。優化
咱們來看看 mysql 該是怎麼選擇索引的,這裏有三個索引可用,分別是主鍵索引、索引a、索引b。ui
若是選擇主鍵索引雖然能夠減小回表過程,可是隻能走全表掃描,須要掃描 10w 條記錄。spa
若是選擇索引 a,則只需在 a 索引上掃描 1k 條記錄,而後回到主鍵索引上過濾掉不知足 b 條件的記錄,最後再按 b 排序便可。code
若是選擇索引 b,則須要在 b 索引上掃描 5w 條記錄,而後一樣回到主鍵索引上過濾掉不知足 a 條件的記錄,由於索引有序,因此使用 b 索引不須要額外排序。排序
咱們來使用執行計劃看下 mysql 究竟會選擇哪一個索引。
mysql> explain select * from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1;
+----+-------------+-------+-------+---------------+------+---------+------+-------+------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------+---------+------+-------+------------------------------------+
| 1 | SIMPLE | t | range | a,b | b | 5 | NULL | 50128 | Using index condition; Using where |
+----+-------------+-------+-------+---------------+------+---------+------+-------+------------------------------------+
1 row in set (0.12 sec)
複製代碼
能夠看出 mysql 是選擇使用索引 b,雖然掃描行數要多一些,但由於索引自己是有序的,使用索引 b 能夠避免排序,mysql 認爲這個排序的代價高於掃描行數。
上面這個選擇是 mysql 優化器內部的分析,那麼實際狀況又如何呢,咱們能夠分別執行一下 sql 語句,使用 force index(a) 強制使用索引 a 來對比下,看下二者具體花費的時間。
mysql> select * from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1;
Empty set (0.65 sec)
mysql> select * from t force index(a) where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1;
Empty set (0.05 sec)
複製代碼
從結果能夠看到其實使用索引 a 顯然速度會更快,因此這就是屬於 mysql 選錯了索引的狀況,那咱們怎麼避免這種狀況呢,咱們能夠把 sql 語句改爲下面這樣的,即把 order by b 改爲 order by b,a 。
select * from t where a between 1 and 1000 and b between 50000 and 100000 order by b,a limit 1;
複製代碼
這樣的話,在 mysql 看來不管是使用索引 a 仍是索引 b 都須要排序了,那就只能選擇掃描行更少的索引了,因此 mysql 會選擇索引 a,從而達到避免 mysql 選錯索引的目的,咱們能夠看下優化後的這條 sql 的執行計劃。
mysql> explain select * from t where a between 1 and 1000 and b between 50000 and 100000 order by b,a limit 1;
+----+-------------+-------+-------+---------------+------+---------+------+------+----------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------+---------+------+------+----------------------------------------------------+
| 1 | SIMPLE | t | range | a,b | a | 5 | NULL | 999 | Using index condition; Using where; Using filesort |
+----+-------------+-------+-------+---------------+------+---------+------+------+----------------------------------------------------+
1 row in set (0.00 sec)
複製代碼
大多數狀況下,mysql 都會選擇正確的索引,選錯索引算是比較少見的特殊狀況了,文中的例子也是個特例,僅是給你們提供一個分析思路,當你遇到一些已經使用了索引但依然比較慢的 sql 語句的時候,能夠嘗試分析是不是 mysql 選錯了索引的緣由。
其實還有一些狀況,會致使 mysql 選錯索引,就是 mysql 預估掃描行的數據不夠準確,而這個不許確一般是數據表有頻繁的刪除或更新操做致使的數據空洞形成的,關於這個緣由,我會在後面再詳細講。
這篇文章若是對你有些啓發,不妨點個贊吧,感謝支持,固然若是對文中有不太明白的地方,歡迎留言。另外,不知道你們對 explain 這個命令熟悉不,若是不熟悉的話,我考慮再單獨寫一篇關於 explain 使用的文章。