Left join+or exists+like关联优化

1、问题

这周在项目中遇到这样的sql

bash 复制代码
select count(1) 
  from (select t2.*, 
                    row_number()over( 
                        partition by t2.pid 
                            order by null) rn
               from t2
          left join (select *
                       from t1
                      where t1.flag=1
                        and (t1.nname like 'ABC10%'
                                 or t1.nname like 'ABC2%'
                                 or t1.nname like 'ABC3%')) tt
                 on t2.pid=tt.pid
              where t2.flag in (1,2,3,4) and (exists (select 1 from t3 where t3.flag=1 and t3.uid='U1' and t3.pid=t2.pid)
                 or exists (select 1
                       from t4
                      where t4.flag=1
                        and t4.uid='U1'
                        and t2.udcode like t4.dcode||'%') )) 
 where rn=1

计划:

从计划中可以看到

(1)生成HEAP TABLE,即是临时表用来临时存放中间结果集(可以理解为with 表),优化器会将公共部分提取处理生成HEAP TABLE,为了减少扫描次数而做的优化

HEAP TABLE主要是T1和T2表关联,即主查询,or条件会让公共部分多次扫描,所以优化器考虑生成HEAP TABLE减少扫描次数,但HEAP TABLE会造成条件没有提前过滤引起性能问题,我们可以看到都是全表扫(CSCN)

而从语句中得知exists中存在UID过滤性较好的条件,可以考虑优化先做exists后,t2表再与t3表关联,or 用union all合并

值得注意一点union all是不会去重,所以结果可能出现重复结果,但是RN = 1做了去重操作,所以最终改写结果符合预期。

(2)另外like关联再inner join被驱动表才有可能利用上索引,可以理解为t2.udcode like t4.dcode||'%' 是将t4.dcode当成var(参数),过去分享中可以知道like模糊匹配,通配符在后面可以利用索引优化。所以可以从这点考虑将exists改写成inner join

简单介绍exists和inner join转换

exists是判断主查询(左表)是否存在满足子查询关联条件的数据,是则返回该行结果,只是判断真假,重复也只返回主查询的数据,不受子查询重复数据影响

那么其实就是内连接右表去重后的结果

最终结果与exists一样

所以得知exists子查询转换成inner join需要将子查询结果去重后作为inner join右表进行关联。

2、改写

bash 复制代码
select count(1) 
  from (select * , 
                    row_number()over( 
                        partition by pid 
                            order by null) rn
          from (select t2.*
               from t2
          left join (select *
                       from t1
                      where t1.flag=1
                        and (t1.nname like 'ABC1%'
                                 or t1.nname like 'ABC2%'
                                 or t1.nname like 'ABC3%')) tt
                 on t2.pid=tt.pid
              where t2.flag in (1,2,3,4) and exists (select 1 from t3 where t3.flag=1 and t3.uid='U1' and t3.pid=t2.pid)
              union all
             select t2.*
               from t2
          left join (select *
                       from t1
                      where t1.flag=1
                        and (t1.nname like 'ABC1%'
                                 or t1.nname like 'ABC2%'
                                 or t1.nname like 'ABC3%')) tt
                 on t2.pid=tt.pid
          inner join (select distinct  t4.dcode
          from t4 where t4.flag=1
          and t4.uid='U1'
           ) t4 on  t2.udcode like t4.dcode||'%'   
            where t2.flag in (1,2,3,4) ) tt )
where rn=1

计划:

性能提升几倍。

3、小结

(1) Left join+or exists,以及多个or条件存在,优化器会认为生成HEAP TABLE可以优化,当exists存在过滤性较好的条件,可以考虑将or exitst改成union all来合并,但值得注意union all与or并非完全等价,需要考虑去重。

(2) Like关联,通配符在后,相当于模糊匹配通配符在后可以用索引优化,如果是关联结果数据较少,可以考虑exists转换成inner join去优化。

(3) 项目中遇到的例子是各种性能问题综合起来的,需要我们逐一用过去所学到的优化去灵活处理,并非所有的都是单向优化。

4、附加测试数据

bash 复制代码
create table t1 (id varchar2(36) primary key,pid varchar2(20),nname varchar2(20),flag int);
insert into t1 select sys_guid(),'A'||to_char(round(dbms_random.value(1,10000),0)),'ABC'||to_char(round(dbms_random.value(1,500),0)),
round(dbms_random.value(1,5),0) from dual connect by level<=100000;
commit;
create table t2 (id varchar2(36) primary key,pid varchar2(20),udcode varchar2(10),flag int);
insert into t2 select sys_guid(),'A'||to_char(round(dbms_random.value(1,10000),0)),'0'||to_char(round(dbms_random.value(301,321),0)),
round(dbms_random.value(1,5),0) from dual connect by level<=200000;
commit;
create table t3 (id varchar2(36) primary key,pid varchar2(20),uid varchar2(20),flag int);
insert into t3 select sys_guid(),'A'||to_char(round(dbms_random.value(1,50000),0)),'U'||to_char(round(dbms_random.value(1,5000),0)),
round(dbms_random.value(1,5),0) from dual connect by level<=100000;
commit;
create table t4 (id varchar2(36) primary key,pid varchar2(20),dcode varchar2(10),uid varchar2(20),flag int);
insert into t4 select sys_guid(),'A'||to_char(round(dbms_random.value(1,20000),0)),'0'||to_char(round(dbms_random.value(301,321),0)),'U'||to_char(round(dbms_random.value(1,5000),0)),
round(dbms_random.value(1,5),0) from dual connect by level<=100000;
commit;
dbms_stats.gather_table_stats(USER,'T1',null,100);
dbms_stats.gather_table_stats(USER,'T2',null,100);
dbms_stats.gather_table_stats(USER,'T3',null,100);
dbms_stats.gather_table_stats(USER,'T4',null,100);
create index IDX_DM_T3_uid on t3(uid);
create index IDX_DM_T4_uid on t4(uid);
create index IDX_DM_T2_pid on t2(pid);
create index IDX_DM_T2_udcode on t2(udcode);
create index IDX_DM_T1_pid on t1(pid);
create index IDX_DM_T1_nname on t1(nname);
相关推荐
我科绝伦(Huanhuan Zhou)1 小时前
Oracle 表空间碎片整理实战:shrink space 与 move table 全解析
数据库·oracle
Yyyyyy~2 小时前
【Mysql】表的操作
数据库·mysql
TDengine (老段)2 小时前
工业数据十大痛点系列 · 第 6 篇
数据库·时序数据库·tdengine·涛思数据·idmp·工业数据分析·ai 分析数据
ly76892 小时前
PostgreSQL 流复制与高可用切换:主从延迟、同步提交与 Spring Boot 读写分离的边界
数据库·spring boot·postgresql·读写分离·流复制·高可用切换
l1t3 小时前
修改两处,让SQLite求解数独SQL在datafusion上运行
服务器·数据库·sql
data analyse 4563 小时前
服务端统计时区切换的日期归并
大数据·前端·数据库·人工智能·数据分析
远方16093 小时前
108-ORA-00600 [kdsgrp1-qetlbr] 内部错误排查与 RMAN 块级恢复
大数据·linux·数据库·oracle
福娃筱欢4 小时前
SQLServer迁移至KES SQLServer模式 问题整理
数据库·oracle·sqlserver
hflmwl84 小时前
RedisDesktopManager安装包中文版官方下载,redis可视化客户端工具下载
数据库·redis·缓存·可视化客户端工具