1. 程式人生 > >一個跨庫複雜查詢的SQL優化的案例

一個跨庫複雜查詢的SQL優化的案例

 導讀
為了幫客戶出一個報表,需要跨三個庫進行一個複雜的查詢,用到了 in,union all,left join等關鍵字,
其中幾個欄位還需要進行SUM,Count等計算。未優化前查詢耗時368秒。
待優化場景
先看一下客戶程式設計師寫的這個複雜的SQL語句吧,看了都頭大。雖說輝哥見多識廣,還是被這個SQL嚇到了。


select distinct a.cn,e.chinese_name new_type,d.chinese_name partner,a.csum,b.region_name region,c.login_account saler,
#已出庫成本 -->
(select IFNULL(SUM(purch_price),0.0000) from stock_info where is_delete='N' and out_voucher_no in
(select voucher_no from normal_outbound where is_delete='N' and contract_examination_status='5' and cn=a.cn
union all
select voucher_no from abnormal_outbound where is_delete='N' and contract_examination_status='5' and cn=a.cn
)) outbound_cost,
#銷售合同成本核算成本 -->
(select IFNULL(SUM(price_contract),0.0000) from sale_contract_cost where is_delete='N' and sale_contract_id=a.cn)
sale_contract_cost,
#已出庫銷售金額 -->
'0.0000' out_sale_amount,
#合同結算金額 -->
(select IFNULL(SUM(con_set_amount),0.0000) from cinout_info where is_delete='N' and cn=a.cn)
con_set_amount,
#已收款金額 -->
(select IFNULL(SUM(money),0.0000) from receivable_recorder where is_delete='N' and cn=a.cn) 
actual_pay,
#未收款金額=合同結算金額-已收款金額 用Excel公式計算 -->
'0.0000' not_actual_pay,
#已開票金額 -->
(select IFNULL(SUM(invoice_amount),0.0000) from invoice_info where is_delete='N' and cn=a.cn)
invoice_amount,
#合同簽訂數量 -->
(select sum(quantity) from sale_contract_cost where is_delete='N' and sale_contract_id=a.cn)
contract_quantity,
#實際出庫數量 -->
(select COUNT(sn) from stock_info where is_delete='N' and out_voucher_no in
(select voucher_no from normal_outbound where is_delete='N' and contract_examination_status='5' and cn=a.cn
union all
select voucher_no from abnormal_outbound where is_delete='N' and contract_examination_status='5' and cn=a.cn
)) outbound_quantity,
#未開票金額=合同結算金額-已開票金額 -->
'0.0000' not_invoice_amount,
'' memo

from db0.sale_contract_info a 
left join db1.region_info b on a.region=b.uuid
left join db1.user_info c on a.saler_id=c.uuid
left join db1.partner_info d on a.partner_id=d.code_by_system
left join db2.jrunion_dictionary e on a.new_type=e.english_name
where 
a.is_delete='N' and a.is_close='N' and a.is_z



SQL優化思路
想要優化一個SQL,一般來說就是先看執行計劃,觀察是否儘可能用到索引,
同時要關注預計掃描的行數,以及是否產生了臨時表(Using temporary) 
或者 是否需要進行排序(Using filesort),想辦法消除這些情況。
更進一步的優化策略則可能需要調整程式程式碼邏輯,甚至技術架構或者業務需求,
這個動作比較大,一般非核心繫統上的核心問題,不會這麼大動干戈,絕大多數情況,

還是需要靠DBA儘可能發揮聰明才智來解決。

優化思考
首先觀察這個查詢SQL語句,貌似複雜,其實有規律可尋。我們分成三部分來看,即select部分,from部分和查詢條件部分。
其中比較複雜的是select部分,很多欄位是通過子查詢,計算和聯合獲得的。
其次通過分步測試,發現加上“已出庫成本outbound_cost”和“實際出庫數量outbound_quantity”這兩個欄位查詢比較慢,
再對這兩個欄位的子查詢分析,如果去年其中的查詢條件cn=a.cn,查詢速度可以大大提高。但是這樣查出來的資料經過
計算是錯誤的。據此我懷疑是不是因為a的定義是在子查詢外面兩層,導致查詢速度降低。於是我決定將這兩個欄位的子查詢語句
做成兩個view,再從這兩個view裡查詢。這樣,子查詢就不用到外層去查詢a表和a表的cn欄位去對比,資料也不會出錯。
改完這塊之後,查詢速度果然提高了近10倍,查詢耗時縮短為39秒左右。但是這離客戶要求的3-4秒還很遠。
按照老葉(mySQL培訓機構知數堂的創始人,國內著名mySQL專家)的提示,查看了下執行計劃,發現所有表中都沒有建索引。按照老葉的指導,建立了一堆單一和聯合索引。再試,2.9秒!速度差不多又提高了15倍。


後記
見到複雜的SQL語句,請拍拍左胸,提醒自己不要害怕。然後心中默唸幾句:我是無所不能,魔擋殺魔,佛擋殺佛的全棧DBA。

老葉說過:絕大多數的SQL通過新增索引、適當調整SQL程式碼等簡單手法來完成。本例中查詢效率之所以能提高100多倍,其中
索引有一大半的功勞,另外兩個檢視的建立也不可忽略。