1.故事背景
使用mysqldumpslow監控到一個列表慢sql,EXPLAIN sql 顯示Type爲index,key爲排序字段索引,看解釋結果應該不會慢。
2.數據交代
-
表結構及全表數據(總數據32w)
CREATE TABLE `terminal` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`term_sn` varchar(36) NOT NULL,
`modifydate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `term_sn` (`term_sn`),
KEY `modifydate_id` (`modifydate`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
3.慢sql分析
-
sql執行時間(31s)
- sql解釋 -因爲term_sn使用了 like %(%在左邊) ,所以用不上term_sn索引,Mysql就會自動選擇order by字段的索引(modifydate_id索引)
而當我們強制不使用modifydate_id索引時,查詢速度爲1.4s,兩者查詢速度比爲31.4/1.4=22.4(倍)
解析不使用索引的sql
現象:使用filesort比不使用filesort 效率更高
4.原理分析
-
使用order by 索引的sql分析
select * from terminal where term_sn like '%0820197670%' order by modifydate desc limit 15;
索引用於ORDER BY子句時,會直接遍歷該索引的葉子節點鏈表(B+樹索引詳情見文章底部)。執行流程如下:
1.從modifydate索引的第一個葉子節點出發,按順序掃描所有葉子節點獲取真實的行數據(具體操作見第二步)
2.根據每個葉子節點記錄的主鍵id去主鍵索引(聚簇索引))找到真實的行數據,判斷行數據是否滿足WHERE子句的term_sn條件,若滿足,則取出並返回
第一步依次順序獲取索引樹的葉子節點,這一步很快,但是第二步,由於rowid是根據modifydate進行排序的,第二次去查找真實的行數據,會按照rowid亂序去讀取行記錄,這些行數據在磁盤的存儲是分散的,每讀一行都會產生尋址時延(磁臂移動到指定磁道)+旋轉時延(磁盤旋轉到指定扇區),因此使用了order by 索引的情況下會很慢,這個本質就是隨機I/O。
注:並且此時查詢效率跟滿足WHERE子句的term_sn條件的數據多少/滿足條件數據的修改時間有關。
如果模糊搜索條件爲term_sn like '%0820%'(滿足該條件的數據特別多),上述sql查詢也會很快, 因爲limit和order by 子句或者group by子句聯合使用,mysql都對limit操作的查詢實行了懶惰策略,指要查詢的結果達到了length,就不再據需往下操作了,而我們上述的sql能查的數據小於15條,則會遍歷整個表。
如果我們需要搜索的term_sn like '%0820197670%' 滿足的這條記錄修改時間靠近當前時間,上述sql查詢也會變快,因爲我們使用modifydate倒序,能夠更快查到數據
-
禁止order by 索引的sql
select * from terminal ignore index(modifydate_id) where term_sn like '%0820197670%'
order by modifydate desc limit 15;
禁止使用order by 索引的sql時,先掃表篩選出符合條件的數據,再將篩選結果根據modifydate排序 。執行流程如下
1.掃描全表篩選出滿足WHERE子句的term_sn條件的所有數據行,生成一張臨時表放入排序緩衝區
2.對臨時表緩衝區裏的數據進行排序
第一步雖然進行了全表掃描,但是這一步遍歷使用的是順序IO ,相對與上面的隨機IO會快很多。而且因爲過濾後的臨時表數據很少,使用filesort排序也會很快。
備註:
B+樹索引:InnoDB存儲引擎以B+樹作爲索引的底層實現,B+樹的葉子節點存儲着所有數據頁而內部節點不存放數據信息,並且所有葉子節點形成一個(雙向)鏈表。
舉個例子,假設userinfo表的userid字段上有主鍵索引,且userid目前的範圍在1001~1006之間,則userid的索引B+樹如下:(這裏只是爲了舉例,下圖忽略了InnoDB數據頁默認大小16KB、雙向鏈表,並且假設B+樹度數爲3、userid順序插入)
5.解決方案
1.去除modifydate索引
2.根據是否存在搜索條件判斷是否禁止使用modifydate索引
參考資料
https://segmentfault.com/a/1190000015987895