優化了一半的SQL

    某次在給某知名通信設備供應商作性能優化,快接近尾聲的時候,偶然發現一個不是很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_IDDMD_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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索