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);
相关推荐
黄俊懿15 分钟前
【架构师从入门到进阶】第五章:DNS&CDN&网关优化思路——第八节:网关-注入攻击与预防
sql·网络安全·架构·系统架构·架构师·架构设计
betazhou32 分钟前
mysql shell8.4.10+mysql 8.4.10搭建MySQL replicaset主从
数据库·mysql·ffmpeg·replicaset·mysql shell
zcmodeltech1 小时前
反应装置模型控制系统设计与实现:多设备协同联动方案
java·网络·数据库·stm32·嵌入式硬件·能源·制造
Elastic 中国社区官方博客1 小时前
从建议到修复的 4 个阶段:使用 Elastic Workflows 实现人在回路中的自动化
运维·数据库·人工智能·后端·elasticsearch·ai·自动化
北京靠谱的GEO优化机构1 小时前
海外推广:没有媒体内容,SEO就是“无米之炊“
服务器·数据库·媒体
2601_962071571 小时前
数据库系统架构与DBMS功能探微:现代信息时代数据管理的关键
java·开发语言·数据库
circuitsosk2 小时前
从零搭建行业知识平台:向量库+图数据库+传统关系库的多模检索统一层设计
数据库·python·oracle·向量数据库·rag·多模检索
程序员夏洛2 小时前
Redis 中如何实现分布式锁?
数据库·redis·分布式
2601_962065252 小时前
2.启动mysql容器
java·数据库·mysql