SQL優化案例精彩連載(一)
導讀
知數堂致力於為學員的職場之路保駕護航,課上課下不遺餘力為廣大學員排憂解難!
SQL優化課鄭鬆華老師根據幫學員優化的案例為原型,和大家分享各式優化思路,本文為連載系列第一篇,將不定期陸續更新,敬請關注!
待優化場景
執行計劃見下:
該表的幾個索引見下:
SQL優化之路
首先,我們先review下這個SQL:
mysql> select id, user_id from rs
where id> 6350261
and mutual_plan_id= 1
and state= 10
and actived_at< '2017-04-21'
and mod(id, '16')= '5'
order by id asc limit 5000\G
從執行計劃中 Extra 部分有 Using filesort ,而且rows 有將近100w,說明索引選用的選擇率很不理想;並且這個SQL的最大效能瓶頸就是額外排序,並且條件中有函式不能用到索引。
其次,我們要考慮,為什麼會出現這種問題呢 ?
我們從表結構中可以得出 id 為主鍵,WHERE 條件裡含有 id 列,按理應該選擇主鍵索引才對,但執行計劃中並沒選中它,因為條件中還包含了 mod(id, '16') = '5' ,導致優化器認為同時存在函式轉換,沒有優先選擇主鍵索引
第三步,既然我們找到原因了,那就改寫下SQL,好讓優化器能優先選擇主鍵索引。
SQL改寫的做法是:
改造成子查詢;
將 mod(id, '16')= '5' 條件放在子查詢外層;
在子查詢裡增加 distinct(大家猜一下distinct的作用是什麼呢)。
第四步,我們再看下新的SQL及其執行計劃:
mysql> select
a.id
from (
select distinct id from rs force index (primary )
where id> 6350261
and mutual_plan_id= 1
and state= 10
and actived_at< '2017-04-21'
)a where mod(id, '16')= '5'
limit 5000
執行計劃如下:
可以看到,Using filesort 被消除了。
順便說下,前面加上 distinct 的作用是為了 防止檢視合併。
最後,我們利用子查詢 以及 LIMIT 1 將整個SQL 改寫成如下:
mysql> select
b.id ,(select c.user_id from rs c where
c.id= b.id limit 1) user_id from (
select
a.id
from (
select distinct id from rs force index (primary )
where id> 6350261
and mutual_plan_id= 1
and state= 10
and actived_at< '2017-04-21'
)a where mod(id, '16')= '5'
limit 5000
) b
執行計劃見下:
優化結果
SQL執行時間由修改之前的 12s 多下降到 6s 內。
因為 WHERE條件中有 MOD() 函式,沒辦法用到索引,所以提升效果不是太明顯。
延伸方案
1、如果是用MySQL 5.7版本,可以選擇建立虛擬列並加索引,類似下面的做法:
ALTER TABLE rs ADD modid INT(11) UNSIGNED GENERATED ALWAYS AS ( MOD(id, '16') ) NOT NULL,
ADD INDEX idx_modid(modid);
2、如果是MySQL 5.7以前的版本,則可以考慮把 mod() 函式轉化成子查詢,這個相對麻煩點,我們以後找機會再討論。