形如( c1=? And c2=? ) or (c1=? And c2=?)改写思路

1、问题

最近遇到如下的写法:

bash 复制代码
select count(1) from t1 left join t2 on t1.c1=t2.c1
where ((t1.c2 ='B42' and t1.c3='C181')
or (t1.c2='B3440' and t1.c3='C2241')
or (t1.c2='B3' and t1.c3='C3'));

计划:

执行成功, 执行耗时152毫秒

语句的计划对于(t1.c2 ='B42' and t1.c3='C181')多个组合用的是sscn(索引全扫描),我们可能预期希望做成ssek(索引扫描)方式,可以有两种方式。

2、参数优化

bash 复制代码
select /*+ENABLE_IN_VALUE_LIST_OPT(1)*/count(1) from t1 left join t2 on t1.c1=t2.c1
where ((t1.c2 ='B42' and t1.c3='C181')
or (t1.c2='B3440' and t1.c3='C2241')
or (t1.c2='B3' and t1.c3='C3'));

计划:

执行成功, 执行耗时2毫秒

ENABLE_IN_VALUE_LIST_OPT=1符合预期的计划。那为了彻底解决语句性能问题,自然需要改写替换。前面的文章中提到or在一定场景下可以等价改写成union all,语句or条件拆分成三个部分(1)(t1.c2 ='B42' and t1.c3='C181') (2)(t1.c2='B3440' and t1.c3='C2241')

(3)(t1.c2='B3' and t1.c3='C3'),三者之间彼此独立的,不可能存在重叠,因此可以改写成union all。

3、改写

bash 复制代码
with temp as(
select t1.* from t1 left join t2 on t1.c1=t2.c1
)
select sum(cnt) from
(select count(1) as cnt
from temp t1
where (t1.c2 ='B42' and t1.c3='C181')
union all 
select count(1) as cnt 
from temp t1
where  (t1.c2='B3440' and t1.c3='C2241')
union all 
select count(1) as cnt 
from temp t1
where (t1.c2='B3' and t1.c3='C3')
);

计划:

执行成功, 执行耗时1毫秒.

性能成倍提升。

4、小结

这个语句的思路是我们想要让条件用上索引扫描,从这一点出发,结合or条件改写方法便能找到优化的改写方案。