exists子查询改写思考

1、问题

项目中遇到以下写法

bash 复制代码
select count(1)
  from (select *
          from test1 t1
         where t1.stime>=?
           and t1.etime<=?
           and exists (select 1 from test2 t2 where t2.wpid=t1.id and t2.did=?
               union all
               select 1 from test3 t3 where t3.wpid=t1.id and t3.did=?) )

参数:

2026-08-28 00:00:00

2026-08-30 00:00:00

B1

B1

计划:

这里有过滤性的条件来自exists中,从计划分析,exists中union all的共同部分是t1.id即关联列,优化器会考虑提取公因子即主查询做临时结果集(HEAP TABLE)减少一次扫描。此时合并的结果集和主查询(临时结果集)做hash semi join,而无法单独利用过滤性较好的条件让计划做index join,所以我们可以拆分成单个去和主查询关联,过去的or改写中提到union all可以替代or,反之亦然,可以改写or exists

2、改写1

bash 复制代码
select count(1) 
  from (select * 
          from test1 t1 
         where t1.stime>=to_date(?,'YYYY-MM-DD HH24:MI:SS') 
           and t1.etime<=to_date(?,'YYYY-MM-DD HH24:MI:SS') 
           and (exists (select 1 from test2 t2 where t2.wpid=t1.id and t2.did=?)
               or exists (
               select 1 from test3 t3 where t3.wpid=t1.id and t3.did=?) ));

参数:

2026-08-28 00:00:00

2026-08-30 00:00:00

B1

B1

计划:

执行时间从7s提升至0.017s。

另外我们看原计划它做成HEAP TABLE,优化器会认为提取临时表是减少一次扫描会提高效率,那么我们可以考虑如果把主查询和exists子查询做成同一层,让优化器根据估算去考虑做成index join,所以我们可以把union all把共同列获取后再和主查询关联。

3、改写2

bash 复制代码
select count(1)
  from (select *
          from test1 t1
         where t1.stime>=to_date(?,'YYYY-MM-DD HH24:MI:SS') 
           and t1.etime<=to_date(?,'YYYY-MM-DD HH24:MI:SS') 
           and exists (select 1 
                  from (select t2.wpid from test2 t2 where t2.did=?
                       union all
                       select t3.wpid from test3 t3 where t3.did=?) tt 
                 where tt.wpid=t1.id))

参数

2026-08-28 00:00:00

2026-08-30 00:00:00

B1

B1

计划:

也是符合预期,性能提升至0.013s

4、小结

exists (select 1 from union all select 1 from )写法,exists中有过滤性较好的条件,可以考虑改写成or exists 或union all合并得出关联列结果集,再与主查询做exists。

5、附加测试数据

bash 复制代码
create table test1(id varchar2(36) primary key,stime timestamp,etime timestamp,pcode varchar2(4));
insert into test1 select 'A'||level,SYSDATE-INTERVAL '1' SECOND * TRUNC(DBMS_RANDOM.VALUE(-10000,10000)),SYSDATE-INTERVAL '1' SECOND * TRUNC(DBMS_RANDOM.VALUE(-10000,10000)),'03'
from dual connect by level<=800000;
commit;
insert into test1 select 'A'||(level+800000),SYSDATE-INTERVAL '1' SECOND * TRUNC(DBMS_RANDOM.VALUE(-10000,10000)),SYSDATE-INTERVAL '1' SECOND * TRUNC(DBMS_RANDOM.VALUE(-10000,10000)),'03'
from dual connect by level<=200000;
commit;
create index IDX_DM_TIME on TEST1(stime,etime);
dbms_stats.gather_table_stats(USER,'TEST1',null,100);
create table test2(id varchar2(36) primary key,stime timestamp,etime timestamp,pcode varchar2(4),did varchar2(20),wpid varchar2(20));
insert into test2 select sys_guid(),SYSDATE-INTERVAL '1' SECOND * TRUNC(DBMS_RANDOM.VALUE(-10000,10000)),SYSDATE-INTERVAL '1' SECOND * TRUNC(DBMS_RANDOM.VALUE(-10000,10000)),'03'
,'B'||to_char(round(dbms_random.value(1,30000),0)),'A'||to_char(round(dbms_random.value(1,8000),0))
from dual connect by level<=150000;
commit;
create index IDX_DM_TEST2_WID_DID on TEST2(WPID,DID);
dbms_stats.gather_table_stats(USER,'TEST2',null,100);


create table test3(id varchar2(36) primary key,stime timestamp,etime timestamp,pcode varchar2(4),did varchar2(20),wpid varchar2(20));
insert into test3 select sys_guid(),SYSDATE-INTERVAL '1' SECOND * TRUNC(DBMS_RANDOM.VALUE(-10000,10000)),SYSDATE-INTERVAL '1' SECOND * TRUNC(DBMS_RANDOM.VALUE(-10000,10000)),'03'
,'B'||to_char(round(dbms_random.value(1,12134),0)),'A'||to_char(round(dbms_random.value(1,8000),0))
from dual connect by level<=100000;
commit;

create index IDX_DM_TEST3_WID_DID on TEST3(WPID,DID);
create index IDX_DM_TEST3_DID on TEST3(DID);
create index IDX_DM_TEST2_DID on TEST2(DID);
相关推荐
李兆龙的博客8 小时前
问津集 #26:Lakebase——Postgres 的版本化页面存储、数据库分支与计算弹性
数据库
倔强的石头_10 小时前
聊聊金仓KFS:一款把数据同步软件做扎实的产品
数据库
闲云野鹤在人间10 小时前
MySQL|从理论、安装、备份到主从复制、MHA高可用详解
linux·运维·数据库·mysql·云计算
禾小西10 小时前
Redis:从两大维度和三大主线建立知识体系
数据库·redis·缓存
禾小西11 小时前
Redis 数据结构:快速的 Redis 有哪些慢操作?
数据结构·数据库·redis
数据库小学妹11 小时前
数据共享交换平台选型:交换方式对比与避坑指南
数据库·信创·数据同步·数据交换平台·政务数据共享·数据共享交换平台·数据库底座
要开心吖ZSH12 小时前
MySQL 慢 SQL 排查操作手册-个人笔记版
java·笔记·sql·mysql·慢查询
loong_XL13 小时前
决策模型做内容安全检测:从踩坑到上线
数据库·安全·jev·决策模型
这个DBA有点耶13 小时前
数据库双轨并行实战:全量并行策略、增量延迟控制、双向回切与一致性校验
数据库·架构·dba
adinnet202614 小时前
保单、赔付与渠道问数:保险经营数据如何实现按需查询
大数据·数据库·人工智能