某次在給某知名通信設備供應商作性能優化,快接近尾聲的時候,偶然發現一個不是很TOP的TOP sql(通常劉老師會收集AWR 的TOP 50 sql,默認只有大概20個)使用了Hint,而其餘SQL基本上都沒有使用hint,其中必有隱情。順手分析一下 :sql
雖然SQL平均執行時間0.25秒,可是執行次數多,所以也在TOP50之列。性能優化
SQL:
微信
SELECT /*+PUSH_PRED(HS)*/*oracle
FROM DMD_BOQ_PLAN_HEADER_T DBPH,性能
DMD_PAYMENT_UNIT_V HS,測試
DMD_PAYMENT_UNIT_CONTROL_T PUC優化
WHERE DBPH.PAYMENT_UNIT_ID = HS.PAYMENT_UNIT_IDspa
AND HS.PAYMENT_UNIT_ID = PUC.PAYMENT_UNIT_ID.net
AND DBPH.BOQ_PLAN_HEADER_ID = :B1;索引
說明:
其中 DMD_PAYMENT_UNIT_V是一個view,DDL內容以下:
SELECT STAGE_ID AS PAYMENT_UNIT_ID,......
FROM HT_STAGES
UNION ALL
SELECT DBT.CCM_BOQ_ID AS PAYMENT_UNIT_ID,......
FROM DMD_BOQ_T DBT
WHERE DBT.REGISTER_FLAG ='N';
VIEW使用的兩個錶轉換成的PAYMENT_UNIT_ID字段的對應列(HT_STAGES.STAGE_ID和DMD_BOQ_T.CCM_BOQ_ID),都是選擇性很好的列;SQL謂詞條件使用的幾個字段選擇性也都很是好,字段上都有索引。
根據以上信息,這個SQL的執行時間,正常應該在1毫秒左右,而不該該是AWR報告中顯示的250毫秒。
先來看執行計劃:
時間主要消耗在ID=5的全表掃描上,按照正常的狀況,這一步應該是最後完成,並且是應該使用DMD_PAYMENT_UNIT_CONTROL_T表PAYMENT_UNIT_ID字段上的索引。當前由於這兩個表之間沒有直接關聯關係,這一步的操做至關於作了笛卡爾積,這不科學。ID=7的步驟是正確的。
咱們再來看看沒有使用hint的SQL執行計劃:
這個執行計劃問題更嚴重,由於沒有作謂詞推動(push_pred),view使用的兩個表作了全表掃描,原來SQL使用push_pred的hint仍是起到了重要的優化效果。只是仍沒有解決DMD_PAYMENT_UNIT_CONTROL_T表的全表掃描問題,應該算是一個優化了一半的SQL。
嘗試使用更多的hint來調整執行計劃:
/*+PUSH_PRED(HS) leading(dbph hs puc) use_nl(hs) use_nl(puc) */ 仍然不起做用。
優化嘗試1:
改寫SQL,強制將DBPH和HS放在一個內聯視圖裏先作join(no_merge不能少),而後再與PUC作join,這個是徹底等價的SQL:
select * from
(SELECT /*+ PUSH_PRED(HS) no_merge*/
hs.PAYMENT_UNIT_ID
FROM DMD_BOQ_PLAN_HEADER_T DBPH,
DMD_PAYMENT_UNIT_V HS
WHERE DBPH.PAYMENT_UNIT_ID = HS.PAYMENT_UNIT_ID
AND DBPH.BOQ_PLAN_HEADER_ID = :B1
) hs1,
DMD_PAYMENT_UNIT_CONTROL_T PUC
where HS1.PAYMENT_UNIT_ID = PUC.PAYMENT_UNIT_ID;
這樣改動後,執行計劃就完美了:
這個SQL的執行時間大概就是1ms。
有沒有更好的優化方法?通過測試,答案是有的:
優化嘗試2:
根據等值傳遞原理 a.id=b.id and b.id=c.id 等價於 a.id=b.id and a.id=c.id
將 HS.PAYMENT_UNIT_ID = PUC.PAYMENT_UNIT_ID
改爲 DBPH.PAYMENT_UNIT_ID = PUC.PAYMENT_UNIT_ID
即:
SELECT *
FROM DMD_BOQ_PLAN_HEADER_T DBPH,
DMD_PAYMENT_UNIT_V HS,
DMD_PAYMENT_UNIT_CONTROL_T PUC
WHERE DBPH.PAYMENT_UNIT_ID = HS.PAYMENT_UNIT_ID
AND DBPH.PAYMENT_UNIT_ID = PUC.PAYMENT_UNIT_ID
--AND HS.PAYMENT_UNIT_ID = PUC.PAYMENT_UNIT_ID
AND DBPH.BOQ_PLAN_HEADER_ID = :B1;
通過這樣的修改後,不用任何的hint,執行計劃都是完美的。
這個案例應該是優化器的考慮不周所致,遇到這種狀況,咱們就須要考慮經過改寫SQL來實現優化的目的。
本文分享自微信公衆號 - 老虎劉談oracle性能優化(sql_tigerliu)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。