mysql查詢優化--臨時表和檔案排序(Using temporary; Using filesort問題解決)
先看一段sql:
<span style="font-size:18px;">SELECT * FROM rank_user AS rankUser LEFT JOIN rank_user_level AS userLevel ON rankUser.id = userLevel.user_id LEFT JOIN rank_product AS product ON userLevel.new_level = product.level_id LEFT JOIN rank_product_fee AS fee ON userLevel.fee_id = fee.fee_id LEFT JOIN rank_user_login_stat AS userLoginInfo ON rankUser.id = userLoginInfo.user_id ORDER BY rankUser.create_time DESC LIMIT 10 OFFSET 0</span>
介紹一下這段sql的表的構成:一張主表:rank_user;兩張跟rank_user直接關聯(多張表通過同一欄位最好是主鍵進行關聯)的表:rank_user_level ,rank_user_login_stat ;兩張跟rank_user非直接關聯的表:rank_product ,rank_product_fee 。這段sql看似簡單,但是執行時間卻很長,我們來看一下執行計劃:
執行時間1.45s,可以看到,這段不僅僅掃描全表,而且使用了臨時表,進行了檔案排序。
為了找到原因,我們把排序去掉看一下:
SELECT * FROM rank_user AS rankUser LEFT JOIN rank_user_level AS userLevel ON rankUser.id = userLevel.user_id LEFT JOIN rank_product AS product ON userLevel.new_level = product.level_id LEFT JOIN rank_product_fee AS fee ON userLevel.fee_id = fee.fee_id LEFT JOIN rank_user_login_stat AS userLoginInfo ON rankUser.id = userLoginInfo.user_id -- ORDER BY -- rankUser.create_time DESC LIMIT 10 OFFSET 0
執行時間0.015s,掃描行數67452,果然是排序惹的禍。但是僅僅是排序惹的禍嗎?別忘了這裡有兩張非直接關聯的表,這樣的查詢,如果有查詢條件或者排序分組的時候往往都需要建立臨時表(這個沒有辦法,想想也知道)。為了驗證這個觀點,我們把兩張非直接關聯的表去掉看一下:
SELECT * FROM rank_user AS rankUser LEFT JOIN rank_user_level AS userLevel ON rankUser.id = userLevel.user_id -- LEFT JOIN rank_product AS product ON userLevel.new_level = product.level_id -- LEFT JOIN rank_product_fee AS fee ON userLevel.fee_id = fee.fee_id LEFT JOIN rank_user_login_stat AS userLoginInfo ON rankUser.id = userLoginInfo.user_id ORDER BY rankUser.create_time DESC LIMIT 10 OFFSET 0
執行時間0.003s,掃描行數10,屌爆了有木有,mysql多表直接關聯在沒有其他篩選條件的情況下,查詢速度大大提升,而且排序可以使用create_time這個索引,直接取到前十條。
到了這裡,我想大家應該已經明白第一條sql查詢時間很長的原因了:多表非直接關聯的前提下還要排序。mysql查詢往往最需要優化的地方就是臨時表和檔案排序了。這裡總結一下教訓:
1.mysql查詢存在直接關聯和非直接關聯的問題,這兩種查詢效率差別很大;
2.mysql排序儘量使用索引;
3.mysql多表關聯left join其他表的時候,如果以其他表的欄位作為查詢條件都會產生臨時表;
4.mysql在非直接關聯的基礎上進行排序會很慢,需要進行優化;
知道了問題,我們就好優化了,這裡我給出了兩種方案:
第一種(子查詢,適合子查詢部分不作為查詢條件):
SELECT
rankUser.id, rankUser.qq, rankUser.phone, rankUser.regip, rankUser.channel, rankUser.create_time, rankUser.qudao_key, rankUser.qq_openid, rankUser.wechat_openid,
userLevel.recommend_count,userLevel.end_time,userLevel.new_level,userLevel.`level`,userLevel.new_recommend_count,userLevel.`is_limited`,
(case when userLevel.new_level > 1 then 1 else 0 end) is_official_user,
(select product_name from rank_product where level_id = userLevel.new_level) product_name,
(select period from rank_product_fee where fee_id = userLevel.fee_id) period,
userLoginInfo.last_login, userLoginInfo.login_count, userLoginInfo.login_seconds
FROM rank_user AS rankUser
LEFT JOIN rank_user_level as userLevel on userLevel.user_id=rankUser.id
LEFT JOIN rank_user_login_stat as userLoginInfo ON rankUser.id = userLoginInfo.user_id
ORDER BY
rankUser.create_time DESC
LIMIT 10 OFFSET 0
第二種(非直接關聯轉變成直接關聯,這個要根據業務來定,我這裡rank_product和rank_product_fee兩張表只是為了查詢rank_user_level表中的產品和產品費用資訊,而rank_user_level是一張直接關聯的表,故這裡可以先將這三張表進行合併,然後再和rank_user表進行聯合查詢):
SELECT
*
FROM
rank_user AS rankUser
LEFT JOIN (
select
l.*,p.product_name,f.period
from
rank_user_level l,rank_product p,rank_product_fee f
where
l.new_level = p.level_id
and l.fee_id = f.fee_id
) AS userLevel ON rankUser.id = userLevel.user_id
LEFT JOIN rank_user_login_stat AS userLoginInfo ON rankUser.id = userLoginInfo.user_id
ORDER BY
rankUser.create_time DESC
LIMIT 10 OFFSET 0