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);
相关推荐
YH_Analytics3 分钟前
2026 高压 E-House 市场深度分析报告市场格局
网络·数据库
后台模板学习17 分钟前
MongoDB 与 MySQL 在 IM 即时聊天场景中该
数据库·mysql·mongodb
KaiwuDB1 小时前
企业级数据库数据保护:KaiwuDB KaiwuDB V3.3.0 发布:S3 冷热分层存储与主备集群能力增强实战解析模场景备份恢复方案解析
数据库·时序数据库·kaiwudb·发版·多模时序数据库·冷热分层存储·主备集群
DataY1 小时前
MySQL CDC 直接写进 Doris 汇总模型:同步即汇总,明细不用搬两遍
数据库
琳达爱分享1 小时前
客服改了地址,发货任务把它改了回去:一次没人报错的覆盖
数据库
IT大白鼠1 小时前
渗透测试实战从靶场搭建到渗透流程——第 8 篇 · 服务型靶标(上):MySQL 双版本写文件链
数据库·mysql·渗透测试
piaoyiren2 小时前
Python FTP 服务端项目详细功能文档
网络·数据库·python
少年的范儿2 小时前
Windows 安装 MySQL 8.4 教程(ZIP 解压版):配置 my.ini、初始化数据库与启动服务
数据库·windows·mysql
l1t2 小时前
DeepSeek总结的AI 智能体应该用哪种语言搜索其记忆:SQL 还是 SPARQL?
数据库·人工智能·sql
麦壳饼2 小时前
SonnetDB:PID 调参实战:阶跃响应分析与参数解读
数据库·sonnetdb