天天看點

優化了一半的SQL

某次在給某知名通訊裝置供應商做性能優化,快接近尾聲的時候,偶然發現一個不是很TOP的TOP sql(一般劉老師會收集AWR 的TOP 50 sql,預設隻有大概20個)使用了Hint,而其他SQL基本上都沒有使用hint,其中必有隐情。順手分析一下 :

優化了一半的SQL

雖然SQL平均執行時間0.25秒,但是執行次數多,是以也在TOP50之列。

SQL:

SELECT /*+PUSH_PRED(HS)*/*

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 HS.PAYMENT_UNIT_ID = PUC.PAYMENT_UNIT_ID

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毫秒。

先來看執行計劃:

優化了一半的SQL

時間主要消耗在ID=5的全表掃描上,按照正常的情況,這一步應該是最後完成,而且是應該使用DMD_PAYMENT_UNIT_CONTROL_T表PAYMENT_UNIT_ID字段上的索引。目前因為這兩個表之間沒有直接關聯關系,這一步的操作相當于做了笛卡爾積,這不科學。ID=7的步驟是正确的。

我們再來看看沒有使用hint的SQL執行計劃:

優化了一半的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

這個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來實作優化的目的。