优化了一半的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源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索