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

雖然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毫秒。
先來看執行計劃:
時間主要消耗在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來實作優化的目的。