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

客户端数据库子查询无法展开情况下的改写案列

bestpaydata 2021-04-18
492


某日监控到数据库中有条DML语句对一个核心表锁表时间较长,查看造成长事务的这个语句的执行计划发现了一些问题,我们一起看一下该语句和执行计划(表名已做脱敏处理):


UPDATE T_TAB_ERROR E SET E.STAT = 'S0S'

WHERE E.PAY_ORG = :B1

AND E.ERROR_SOURCE = '1'

AND EXISTS

(SELECT 1

FROM T_TAB_ORDER O

WHERE 1 = 1

AND ((O.STAT IN ('S0R', 'S1R') AND O.ORDER_TYPE = 'OT001') OR

(O.STAT = 'S0C' AND O.ORDER_TYPE= 'OT501'))

AND O.ORDER_ID = E.ORDER_ID)

OR EXISTS (SELECT 1

FROM T_TAB_REFUND R

WHERE R.STAT = 'S0C'

AND R.ORDER_ID = E.ORDER_ID)



可以看到由于子查询之间使用了OR造成oracle优化器无法对子查询进行展开,这时oracle优化器只能按照如下方法生成执行计划:

1.在主查询上取一条记录,然后探测子查询

2.在主查询上再取一条记录然后再探测

3.直到主查询所有记录取完


这里T_TAB_ERROR是张大表,它的PAY_ORG,ERROR_SOURCE谓词列上没有可选的索引而且过滤效果差,经过全表扫描后大约还有348W条记录,作为一张"驱动表"数据量如此的大,

也就是说子查询中的T_TAB_REFUND和T_TAB_ORDER两表要进行348w次的在order_id上的主键查询性能当然比较差,这句sql锁表至少360秒

接下来我们通过使用merge代替update、用union all代替or的常用手段进行改写:

merge into T_TAB_ERROR E

using (select distinct order_id

from T_TAB_ORDER O

WHERE 1 = 1

AND (O.STAT IN ('S0R', 'S1R') AND O.ORDER_TYPE = 'OT001')

OR (O.STAT = 'S0C' AND O.ORDER_TYPE= 'OT501')

union all

select distinct order_id

from T_TAB_REFUND R

WHERE R.STAT = 'S0C') b

on (E.PAY_ORG ='PT0005' AND E.ERROR_SOURCE = '1' and e.order_id = b.order_id)

when matched then

update set STAT = 'S0S'


改写后的执行计划:


现在主查询和子查询进行HASH JOIN,执行时间降至18S!


案列二:

select * from A

where (A.id in x_subquery or A.id not in y_subquery )

and ( A.id in z_subquery or A.id not in w_subquery )

这里同样无法子查询展开,

x和y来自同一张表,而且x是y的子集,z和w来自同一张表 而且z是w的子集


A.id in x_subquery or A.id not in y_subquery具体是什么呢,画个等价图比较好理解,红色框的部分即是查询结果:




那么可以将sql改写为:

select * from A

where A.id not in ( y_subquery minus x_subquery)

and A.id not in ( w_subquery minus z_subquery)

改写后,A可以与子查询的结果做hash anti join,而不是无数次filter,效率大提升,

这个案列中执行时间从5天变成了10分钟!

总结:

  1. where条件中OR出现的时候很容易出现子查询无法展开 可以使用union的方法来代替,避免因or造成执行计划选择filter表连接方式

  2. 普通update语句只适合驱动表为少量数据。如果驱动表过大,可考虑改下成merge into方式,走hash join


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

评论