天天看點

SQL優化三步曲

有一天開發同學回報線上業務庫中有一條SQL執行很滿,每次幾乎要跑1分鐘才結束,希望我們幫忙優化一下,具體SQL如下:

SQL優化三步曲

SQL優化第一步 - 檢視執行計劃

對于一個SQL的優化,我們的第一步也是最重要的一步就是檢視SQL執行計劃,SQL執行計劃一方面告訴我們SQL具體的處理行為,另外一方面也可以展現每個執行步驟下大緻的資源消耗點。是以我們拿到問題SQL以及對應資料庫環境後,登入該環境隻讀執行個體進行SQL分析測試。

SQL優化三步曲

從以上的SQL執行計劃我們可以擷取到哪些有效資訊呢?

  • SQL先對t表掃描查詢生産派生表,brand通過索引過濾作為表關聯的驅動表,與vender、product、shop_product、spu、進行關聯查詢,表關聯均有效利用索引。
  • 從type字段上看,SQL基本上都有效利用到了索引,但是index其實是全索引掃描,該方式的索引掃描執行效率并不會很好
  • 對t表的index全索引掃描資料量高達480w,在目前SQL中執行消耗最大,這也将是我們SQL優化的切入點
  • t表在源SQL中并未出現,再次仔細觀察SQL可以發現SQL引用了view_prod_store_sum的視圖

SQL優化第二步 -

在MySQL中對于視圖使用我們需要知道的是雖然MySQL對視圖的查詢做了一些優化,但是對于複雜視圖查詢其優化支援仍然不是很好,是以業務上我們要盡量避免對複雜視圖的使用。在本SQL中視圖其實是對單表的查詢,且目前SQL資源消耗的瓶頸點也在視圖查詢這部分,是以我們将視圖的定義通過子查詢代替原視圖,整體的來看SQL。

視圖定義:

SQL優化三步曲

完整SQL:

SQL優化三步曲

SQL優化第三步 - 适當改寫

結合我們擷取到的SQL執行計劃以及恢複出來的完整SQL,我們再次了解目前SQL的處理行為:

1)對t表進行全索引掃描,生産派生表2

2)brand表通過brand_id IN (252)條件進行索引過濾,後續與其他表以及派生表2進行關聯查詢

  • 資源消耗點分析:

從SQL真正需要查詢的資料來看,我們隻需要先通過where子句中表過濾條件過濾擷取初步滿足條件記錄,然後對這些記錄判斷 sum(store) > 0是否滿足,滿足則傳回。但是該SQL實際處理卻是先将t表中所有記錄的store進行分組計算,将結果儲存在派生表中。通過where子句中表過濾條件後的記錄再與派生表關聯判斷sum(store)。

SQL在處理的過程似乎掃描了很多不必要的資料,我們為何不僅僅對已滿足where子句條件過濾的記錄做sum(store)判斷呢?

基于以上的分析,我們嘗試使用exists相關子查詢進行改寫測試。為什麼使用相關子查詢呢,這是因為exists在處理SQL時的核心思想是先對where 前的主查詢詢進行查詢,然後用主查詢的結果一個一個的代入exists的查詢進行判斷。 是以我們可以有效的利用exists避免的避免掉優先對t表的派生表産生,保證SQL優先通過where子句中選擇性最佳的條件做驅動表,然後對sum(store)通過相關子查詢進行判斷。

  • 具體改寫如下:
    SQL優化三步曲
  • 改寫後的執行計劃:
  • 執行效率對比

優化前:

SQL優化三步曲

優化後:

SQL優化三步曲

在一般業務SQL編寫中,我們都推薦開發同學使用join而不是exists,這是因為exists本身處理SQL的方式下如果where條件處理後外表記錄仍然很大的情況下,再次将外表中每條記錄代入exists子查詢中判斷,其資源消耗代價是很大的。是以我們更偏向使用JOIN,在滿足必要的索引情況下MySQL優化器優先選擇小表進行驅動。無論具體選擇什麼方式,其實減少掃描函數才是王道!