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

SQL之美第五篇:merge into优化

SQL之美 2021-04-20
4183

这是一次快速优化的小case,欢迎喜欢SQL的各位朋友看看放松下。

本周给事业部出报表时,发现了业务问题数据,经过与负责的研发同学沟通后,获得了研发同学提供的问题数据修复SQL(只提取了有问题的部分):

update arc_mgr_detail t set t.dossier_count_t=(select max(d.check_count) from arc_apply_detail d

where t.is_del=0 and d.is_del=0 and t.contract_no=d.contract_no and t.paylist_code=d.paylist_code

and t.arc_receipt_detail_id=d.arc_receipt_detail_id 

and t.dossier_count_t=0 and d.check_count>0)

where t.id in (select t.id from arc_mgr_detail t ,arc_apply_detail d

where t.is_del=0 and d.is_del=0 and t.contract_no=d.contract_no and t.paylist_code=d.paylist_code

and t.arc_receipt_detail_id=d.arc_receipt_detail_id 

and t.dossier_count_t=0 and d.check_count>0);

执行计划如下:

执行了10分钟没有跑出来,眉头一皱,好玩了, 又到了优化时刻!

之前记得在公司SQL培训中多次说过,关联更新百万级以下数据,update直接改成merge into写法就好了,说干就干

(记住MERGE INTO 基础语法,不用看SQL业务逻辑 直接套)

merge into (select arc_receipt_detail_id,contract_no,dossier_count_t,t.paylist_code 

 from  arc_mgr_detail t where  t.id (select  t1.id

                  from arc_mgr_detail t1, arc_apply_detail d1

                 where t1.is_del = 0

                   and d1.is_del = 0

                   and t1.contract_no = d1.contract_no

                   and t1.paylist_code = d1.paylist_code

                   and t1.arc_receipt_detail_id = d1.arc_receipt_detail_id

                   and t1.dossier_count_t = 0

                   and d1.check_count > 0)   

                  ) t3

using (select     max(d2.check_count) MCOUNT ,D2.contract_no,D2.arc_receipt_detail_id,D2.paylist_code

          from arc_apply_detail d2, arc_mgr_detail t2

                 where t2.is_del = 0

                   and d2.is_del = 0

                   and t2.contract_no = d2.contract_no

                   and t2.paylist_code = d2.paylist_code

                   and t2.arc_receipt_detail_id = d2.arc_receipt_detail_id

                   and t2.dossier_count_t = 0

                   and d2.check_count > 0   

           group by D2.contract_no,D2.arc_receipt_detail_id,D2.paylist_code)  d3

on (t3.contract_no = d3.contract_no and  t3.arc_receipt_detail_id = d3.arc_receipt_detail_id

and t3.paylist_code = d3.paylist_code)

   when matched then 

   update set t3.dossier_count_t =d3.MCOUNT;

咔咔咔 几分钟改完,一副好像没问题的样子,让我们先看一眼改完后的执行计划:

什么,TIME居然999?  虽然是假的,大部分情况下还是能说明这个SQL的性能糟糕程度的。

抱着万一的希望,执行了一波。

果然,7分钟还没跑完,奇迹木有发生。是时候认真看下了:

首先,让我们拆一波 看看数据量:


要修改的数据只有568条,查询速度还凑合(只针对本次一次性更新操作而言),去掉统计后的执行计划如下:

可以看出都是全表扫描速度依然很快。

下面对应的条件也只有564条,拆开来每个部分速度都很快。

去掉统计后的执行计划如下:


这时候,对比前面time 999的执行计划,老司机们基本都看出问题所在了:

这一步计算出现的性能问题,可以看到他没有谓词信息,那么要看它到底对应SQL哪一步,我们就需要明白整体SQL的表检索链接顺序:

首先是d3里面的表先行计算完毕,8976105 形成一个视图

然后是t表传值到t1表(这就是隐含谓词推入),然后t1表跟d1表进行hash关联 14 13 15 12

接下来是t1与d1的关联结果集作为谓词推入前面的视图中先行计算11

最后就是计算完成的结果跟t表进行NL半连接更新啦


说到这里,老司机们基本很明确了。是的 就是传值搞的事儿,t表的值在执行计划里面居然被估算成1了!(当然。只是估测,这一波如果不准,就需要AE-ROWS执行计划干了,还好。这里很顺利)

那么如何阻止他们之间的传值关系呢?

一般的人直接就 NO_MERGE/NO_UNNEST干一下,想着 让T1跟D1自己先关联 别直接找T关联了,思路是好的。

然而, ORACLE的CBO这时候很[机智],你rows预估=1 我一定要主键传值进来。

为了阻止它,我们可以使用hint  full,让t1不走索引了,这样就无法传值进来了。

merge into (select arc_receipt_detail_id,contract_no,dossier_count_t,t.paylist_code 

 from  arc_mgr_detail t where  t.id in (select *+ FULL(t1)  */t1.id

                  from arc_mgr_detail t1, arc_apply_detail d1

                 where t1.is_del = 0

                   and d1.is_del = 0

                   and t1.contract_no = d1.contract_no

                   and t1.paylist_code = d1.paylist_code

                   and t1.arc_receipt_detail_id = d1.arc_receipt_detail_id

                   and t1.dossier_count_t = 0

                   and d1.check_count > 0)   

                  ) t3

using (select     max(d2.check_count) MCOUNT ,D2.contract_no,D2.arc_receipt_detail_id,D2.paylist_code

          from arc_apply_detail d2, arc_mgr_detail t2

                 where t2.is_del = 0

                   and d2.is_del = 0

                   and t2.contract_no = d2.contract_no

                   and t2.paylist_code = d2.paylist_code

                   and t2.arc_receipt_detail_id = d2.arc_receipt_detail_id

                   and t2.dossier_count_t = 0

                   and d2.check_count > 0   

           group by D2.contract_no,D2.arc_receipt_detail_id,D2.paylist_code)  d3

on (t3.contract_no = d3.contract_no and  t3.arc_receipt_detail_id = d3.arc_receipt_detail_id

and t3.paylist_code = d3.paylist_code)

   when matched then 

   update set t3.dossier_count_t =d3.MCOUNT;


执行计划如下:

嗯 999没了,传值也没了,无需二次计算了,让我们看看结果:

结果很不错。当场解决。

快刀斩乱麻,什么表结构,索引选择性,老司机不用看这么细。速度解决问题才是王道,嘿嘿。


文章转载自SQL之美,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论