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);
相关推荐
.痞老板.21 小时前
mysql初阶--数据库基础
数据库·mysql
城北徐宫1 天前
MySQL约束条件一览
数据库·mysql
ClouGence1 天前
数据库变更审批困局,被这个开源工具解决了
数据库·开源
TDengine (老段)1 天前
TDengine IDMP 十大实用功能(9)
大数据·数据库·人工智能·物联网·时序数据库·tdengine·idmp
starrocks_stella1 天前
EMR Serverless StarRocks Object Table:用 SQL 连接 OSS 文件与多模态 AI
数据库
这个DBA有点耶1 天前
以为自增ID只是有空洞?聊聊双主架构下无声的“数据覆盖”
数据库·mysql·dba
钱栈up1 天前
内网全断,怎么核实高炉和烧结系统的数据库对接信息
数据库
weavecodes1 天前
双层缓存与权限实时生效:织码在线教育系统的 Caffeine + Redis 设计
数据库
bksczm1 天前
InnoDB 页结构详解:页类型、页头页尾、数据行与页目录
数据库·mysql
bksczm1 天前
InnoDB 行格式详解:DYNAMIC / COMPACT / COMPRESSED / REDUNDANT
java·开发语言·数据库