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



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