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);
相关推荐
达梦数据7 小时前
达梦数据复制软件DMDRS搭建部署示例:源数据库DM8到目标数据库DM8双向数据同步
数据库
A-刘晨阳7 小时前
32 位 XID 的“狗皮膏药”被撕掉了:金仓 V9 的 64 位事务号实测与底层拆解
数据库
cspttty8 小时前
2026秋招数据审计岗能力栈:SQL、Excel、审计流程与数据治理
大数据·数据库
MayBaymax8 小时前
MongoDB 基础概念
java·数据库·mongodb
2601_962218618 小时前
万象生鲜系统多终端统一数据协议PC手机PDA数据实时同步
大数据·数据库·人工智能·python·算法
我滴老baby8 小时前
部署 Excalidraw:Docker 搭建手绘白板,再配置固定公网访问
数据库·人工智能·架构
迷枫7128 小时前
MySQL 迁移到达梦过程中遇到的一些问题记录
sql
程序猿乐锅8 小时前
【黑马点评 | 第四篇】Redis缓存雪崩
java·数据库·spring boot·redis·缓存
Sun 32859 小时前
集中式主备中,CM、OM 和 DN 是怎样协作的?
数据库·opengauss·cm·磐维数据库·集中式·om·dn