外連接常見誤區——別以爲把on條件寫到子查詢裏就會快了

  昨天工作中一個同事A寫的存儲過程被配置調度的另一位同事B建議要求修改,將"LEFT JOIN ... ON 條件集"裏的非等值關聯條件寫到一個子查詢裏(之前他是寫到子查詢裏,後來模仿我的代碼習慣寫的),而他這麼改後存儲過程似乎跑得更快了,於是同事A跟我說同事B讓我也改一下。我說完全沒必要改,這麼改根本不能提速,SQL執行計劃都是同一個,怎麼就性能優化了呢?!

  這個問題被好多不懂裝懂的“前輩”以訛傳訛,誤導了多少大好青年。分析、測試、解決問題要講原理,不能單憑某人的隻言片語就照他說的做。下面說一下我是如何進行SQL優化的:

SQL優化主要要看執行計劃,對數據庫基本原理和代數優化也要有一定的瞭解。先按SQL語法的解析順序開始看起,WITH AS->FROM ->JOIN->ON->WHERE->GROUP BY->HAVING->SELECT->ORDER BY。

然後基於表的索引、分區等信息把SQL語句中的條件代碼最優化,之後測試性能是否滿足業務要求。測試時候也不能在生產或預生產環境測試,干擾太大。雖然沒做過測試崗位,但也能理解個大概,正確的測試理念是在完全沒有其他外界干擾的測試環境中單獨測試程序的性能、穩定性、準確性。像同事A這樣,在一個外界干擾因素繁多且如此之大的環境,測試了一下改動後的存過,發現跑得快了,就說是性能有提升,我也是醉了。

如果性能仍不能滿足要求,有相關權限的可以用trace文件追蹤一下,畢竟某些情況下(https://mp.csdn.net/postedit/86289881),SQL執行計劃會失真,與實際執行差別很大。此外,還可以藉助AWR報告,分析程序運行過程中數據庫的整體性能耗費,找出關鍵原因。

對於我要說的主要問題——外連接誤區,分析過程如下:

1、先看FROM後面都接了哪些表,根據表的數據量判斷表的順序是否會影響性能,子查詢(視圖)是否數據量太大佔用太多的TEMP表空間、代碼太長等都是需要考慮的問題。

2、如果涉及多表關聯,建議用(INNER) JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN接ON的形式寫,不要用WHERE COLUMN_A = COLUMN_B(+)的形式。前者是標準SQL語法兼容所有關係型DBMS,更利於閱讀代碼,後者一方面不利於閱讀代碼,一方面屬於Oracle的語法,在數據遷移時會不兼容其他數據庫平臺。

寫LEFT/RIGHT JOIN ... ON時要注意ON條件的寫法。所有JOIN中ON條件里正常的話應該是代表兩張表之間的關聯關係,可等值、非等值,在INNER JOIN中是個特例,條件寫在ON中和WHERE中都是一樣的,但在OUTER JOIN中就不行了。因爲OUTER JOIN的結果集要比INNER JOIN大,比如一個查詢:

SELECT *
  FROM A
  LEFT JOIN B
    ON A.DEPT_ID = B.DEPT_ID
   AND B.DEPT_LVL > 4
 WHERE B.CITY = 'SH';

ON條件中代表TA和TB的關聯關係的僅僅是A.DEPT_ID = B.DEPT_ID,而後面那個B.DEPT_LVL > 4滿不滿足我都要把它查出來,不談WHERE條件的話,不管A表怎麼LEFT JOIN TAB_B表,最終的結果永遠是A表的全集,A有多少種記錄,結果集就有多少種記錄(當B表存在多條數據可以與A表關聯時會出現結果集比A表數據量多的情況),至於結果集中包含的B表字段,滿足ON裏所有條件(A.DEPT_ID = B.DEPT_ID AND B.DEPT_LVL > 4)的爲B表中該字段的值,不滿足的爲NULL。

WHERE 條件用來篩選JOIN操作後的結果集。所以加了WHERE條件的LEFT JOIN會導致最終結果集不再是A的全集,而是滿足WHERE條件的A的子集。

上面那個查詢等同於下面這個查詢,Oracle在語法解析時會自動將非關聯條件作爲限制B表的查詢條件,看到的執行計劃是同一個,耗費、字節、IO、CPU耗費、過濾謂詞自然也是一樣的。沒有現成環境,工作數據庫涉及到保密,不方便展示。不信的可以自己試試!

SELECT *
  FROM A
  LEFT JOIN (SELECT * FROM B WHERE DEPT_LVL > 4) C
    ON A.DEPT_ID = C.DEPT_ID
 WHERE C.CITY = 'SH';

既然同樣的執行過程,爲什麼非要多打一些代碼搞個子查詢(視圖)出來呢?!

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