暂无图片
暂无图片
11
暂无图片
暂无图片
暂无图片

从2小时39分到10毫秒-(Oracle简单的exists改写)

取名浪费我半小时 2020-11-27
6633

      今天在做例行巡检时在awr中看到一条异常sql,如下:

      这条sql跑了3501秒还没有跑完,并且sql语句看起来并不是特别复杂,于是就来分析一下,看看有没有优化空间。


sql文本如下:

select min(priority)

  from xxxperson t

 where '1606270446000' = '1606270446000'

   and exists (select 1

          from xxxpnt m

         where m.idtype = t.idtype

           and m.idno = t.idno

           and m.contno = '880037131628'

           and t.validstate = '1')

    or exists (select 1

          from xxxsured n

         where n.idtype = t.idtype

           and n.idno = t.idno

           and n.contno = '880037131628'

           and t.validstate = '1');

来看看执行计划:

      只能说sql写的不咋地,这种exists的写法,很明显会导致走filter类型的执行计划,即t表有多少条记录,m表和n表就会被执行多少次。为了更好的对比优化前后的性能差异,接下来我们先来看看这条sql真实的执行情况(等待的过程是漫长的):

       没错很夸张,该sql执行总耗时2小时39分钟40秒,逻辑读2874多万,物理读270多万。

      filter真的就这么一无是处吗?当然不是,不过这里就不纠结这个了。结合sql文本和执行计划,凭直觉contno列的选择度应该不会差,可以发现执行计划第6步走了contno列的索引,但第9步没有走上contno列的索引,而是走了idno列的索引,再来看看n表各列的选择度以及索引情况:

     很明显的可以看出,执行计划第9步应该选择IDX_CONTNO这个索引,效率会更高,那么接下来就来验证一下(在第二个exists部分加上强制索引的hint)

select *+ gather_plan_statistics*/min(priority)

  from xxxperson t

 where '1606270446000' = '1606270446000'

   and exists (select 1

          from xxxpnt m

         where m.idtype = t.idtype

           and m.idno = t.idno

           and m.contno = '880037131628'

           and t.validstate = '1')

    or exists (select *+ index (n IDX_CONTNO)+*/ 1

          from xxxured n

         where n.idtype = t.idtype

           and n.idno = t.idno

           and n.contno = '880037131628'

           and t.validstate = '1');

select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));

      从真实的执行计划可以看出,还是走的filter类型的执行计划,执行计划第9步走上了IDX_CONTNO索引,并且44秒就执行完成。而且正如最前面的分析,m表和n表都被启动了169万次(t表的记录数)

       到目前为止这条sql执行时间从2小时39分40秒优化到44秒执行完成,这样就可以了吗?答案是否定的。虽然现在执行时间只需44秒,但我们来看看资源消耗:

可以看到仍然需要1500多万的逻辑读,继续优化:

我们在回到开头讨论的exists,接下来我们将exists进行改写,改写后的sql如下:

select min(priority) from xxxperson t,xxxpnt m ,xxxured n 

           where ('1606270446000' = '1606270446000')

   and m.idtype = t.idtype

           and m.idno = t.idno

           and m.contno = '880037131628'

           and t.validstate = '1' 

   or 

              (n.idtype = t.idtype 

   and n.idno = t.idno

           and n.contno = '880037131628'

           and t.validstate = '1' );

最后再来看看改写之后的sql真实的执行计划:

      你没看错,只需要10毫秒,从真实的执行计划可以看出,改写之后,n表和m表只被启动了一次,且评估行数和真实行数是一致的为1行,因此作为驱动表走nested loop,再加上有选择度很高的索引,效率会非常高。

       最后我们来看看改写之后的资源消耗情况:

       最终该sql的执行时间从最初的2小时39分钟40秒降低到现在的10毫秒,消耗的逻辑读从2874万降低到15,物理读从270万降低到0。虽然案例很简单,但是效果很明显



最后修改时间:2020-12-08 15:10:38
文章转载自取名浪费我半小时,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论