先看一段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
