SQL 优化记录:OR 条件改写后利用现有联合索引

1. 问题现象

现场发现一条查询执行时间较长,SQL 本身并不复杂,最终仅返回 2 行,但实际执行耗时超过 1 分钟。

原 SQL:

sql 复制代码
SELECT *
  FROM zoepres.pres_apply_records_pool t
 WHERE 1 = 1
   AND (
        (t.pres_no = 96462280
         AND t.pres_sub_no = 1
         AND t.exec_time = '2026-08-27 10:00:00.000000')
        OR
        (t.pres_no = 96462280
         AND t.pres_sub_no = 1
         AND t.exec_time = '2026-08-27 20:00:00.000000')
       );

原 SQL 返回:

text 复制代码
2 rows

执行耗时:

text 复制代码
已用时间: 00:01:02.466

2. 查看执行计划

原执行计划关键部分如下:

text 复制代码
1   #NSET2: [11939, 627741->2, 2965]
2     #PRJT2: [11939, 627741->2, 2965]
3       #PARALLEL: [11939, 627741->2, 2965]; scan_type(FULL)
4         #HASH RIGHT SEMI JOIN2: [11939, 627741->2, 2965];
          key_num(3)
          KEY(DMTEMPVIEW_891440050.colname=T.PRES_NO
          AND DMTEMPVIEW_891440050.colname=T.PRES_SUB_NO
          AND DMTEMPVIEW_891440050.colname=T.EXEC_TIME)
5           #CONST VALUE LIST: [1, 2->24, 73]; row_num(2), col_num(3)
6           #CSCN2: [11939, 12540543->12554838, 2965];
            INDEX33560548(PRES_APPLY_RECORDS_POOL); btr_scan(1)

这里最明显的是第 6 行:

text 复制代码
#CSCN2: [11939, 12540543->12554838, 2965]

最终只返回 2 行,但底层累计扫描约 1255 万行。

原 SQL 中实际上只有两组条件,并且:

text 复制代码
PRES_NO      完全相同
PRES_SUB_NO  完全相同
EXEC_TIME    不同

但执行计划没有根据这三个条件进行精准索引查找,而是将两组 OR 条件转换成:

text 复制代码
CONST VALUE LIST
        ↓
HASH RIGHT SEMI JOIN
        ↓
CSCN2

导致底层进行了大范围扫描。


3. 结合 ET 确认主要耗时节点

继续查看 ET:

text 复制代码
行号  OP     TIME(US)     PERCENT
7     CSCN2  61732078     98.93%
6     HRS2     666636      1.07%

其中:

text 复制代码
CSCN2 = 61,732,078 us
占总执行时间 98.93%

可以基本确定,本次 SQL 的主要耗时集中在底层扫描。

原 Statistics:

text 复制代码
logical reads  = 42832
physical reads = 108078
io wait time   = 36586 ms
exec time      = 62465 ms

本次执行中 I/O 等待时间约 36.6 秒,同时底层扫描量又非常大,因此优先考虑减少扫描范围。


4. 初步尝试联合索引

原 SQL 的过滤条件涉及:

text 复制代码
PRES_NO
PRES_SUB_NO
EXEC_TIME

最开始考虑建立:

sql 复制代码
CREATE INDEX IDX_TEST_PRES_APPLY_POOL_01
ON ZOEPRES.PRES_APPLY_RECORDS_POOL
(PRES_NO, PRES_SUB_NO, EXEC_TIME);

执行时报错:

text 复制代码
-3236: 此列列表已索引

说明这组三列实际上已经存在对应索引,因此问题并不是"缺少索引"。

也就是说,当前更值得关注的是:

已经存在合适索引,但原 SQL 的写法没有让优化器使用该索引进行精准范围扫描。


5. 分析 SQL 结构

原条件:

sql 复制代码
AND (
     (t.pres_no = 96462280
      AND t.pres_sub_no = 1
      AND t.exec_time = '2026-08-27 10:00:00.000000')
     OR
     (t.pres_no = 96462280
      AND t.pres_sub_no = 1
      AND t.exec_time = '2026-08-27 20:00:00.000000')
    )

两组 OR 条件中:

text 复制代码
t.pres_no     = 96462280
t.pres_sub_no = 1

完全相同。

真正发生变化的只有:

text 复制代码
t.exec_time

因此可以将公共条件提取出来,将两个时间条件改为 IN。


6. SQL 改写

改写后:

sql 复制代码
SELECT *
FROM zoepres.pres_apply_records_pool t
WHERE t.pres_no = 96462280
  AND t.pres_sub_no = 1
  AND t.exec_time IN (
      '2026-08-27 10:00:00.000000',
      '2026-08-27 20:00:00.000000'
  );

改写只调整了过滤条件表达方式,没有增加索引,也没有修改业务逻辑。


7. 优化后执行计划

改写后仍返回 2 行。

关键执行计划:

text 复制代码
1   #NSET2: [1, 1->2, 2965]
2     #PRJT2: [1, 1->2, 2965]
3       #NEST LOOP INDEX JOIN2: [1, 1->2, 2965]
4         #CONST VALUE LIST: [1, 2->2, 13]; row_num(2), col_num(1)
5         #PARALLEL: [1, 1->2, 2965]; scan_type(FULL)
6           #BLKUP2: [1, 1->2, 2965];
            JPC_PRES_APPLY_RECORDS_POOL_20260402(PRES_APPLY_RECORDS_POOL)
7             #SSEK2: [1, 1->2, 2965];
              JPC_PRES_APPLY_RECORDS_POOL_20260402(PRES_APPLY_RECORDS_POOL)
              scan_range[
              (exp_cast(96462280),exp_cast(1),DMTEMPVIEW_891597895.colname),
              (exp_cast(96462280),exp_cast(1),DMTEMPVIEW_891597895.colname)
              ]

执行方式发生了明显变化。

原来:

text 复制代码
CONST VALUE LIST
        ↓
HASH RIGHT SEMI JOIN
        ↓
CSCN2
        ↓
扫描约 1255 万行

改写后:

text 复制代码
CONST VALUE LIST
        ↓
NEST LOOP INDEX JOIN2
        ↓
SSEK2
        ↓
利用现有联合索引精准查找
        ↓
返回 2 行

此时优化器已经能够使用:

text 复制代码
JPC_PRES_APPLY_RECORDS_POOL_20260402

进行索引范围定位。


8. 优化前后对比

指标 优化前 优化后
返回行数 2 2
执行时间 62465 ms 2.107 ms
logical reads 42832 82
physical reads 108078 0
io wait time 36586 ms 1 ms
主要访问方式 CSCN2 SSEK2
底层访问情况 扫描约 1255 万行 精准命中 2 行

逻辑读:

text 复制代码
42832 → 82

下降约:

text 复制代码
99.81%

执行时间由约 62 秒下降到毫秒级。

需要注意,前后物理读受缓存状态影响较大,因此最终判断优化效果时,主要结合:

text 复制代码
执行计划变化
逻辑读变化
实际扫描量变化

进行确认,而不是只看单次执行时间。


9. 总结

本次 SQL 本身已有可以利用的联合索引,因此问题不在于缺少索引,而在于 SQL 条件的写法。

原 SQL 使用两组 OR 条件:

text 复制代码
(PRES_NO + PRES_SUB_NO + EXEC_TIME)
OR
(PRES_NO + PRES_SUB_NO + EXEC_TIME)

由于前两个条件完全相同,仅 EXEC_TIME 不同,优化器将其转换为常量表与 HASH SEMI JOIN,最终对底层数据进行了大范围扫描。

将公共条件提取,并把时间条件改写为:

sql 复制代码
EXEC_TIME IN (...)

以后,优化器能够直接利用现有联合索引进行 SSEK 精准检索,底层扫描量和逻辑读均明显下降。

本次优化的关键点不是新增索引,而是:

通过等价 SQL 改写,使现有索引真正被有效利用。

相关推荐
拾光Ծ3 小时前
【MySQL】对表数据的操作:增删查改(CRUD)
android·数据库·sql·mysql
迷枫7125 小时前
SQL 优化排查记录:补充索引,利用 OR 展开消除大表重复扫描
sql
九皇叔叔1 天前
《MySQL 体系架构详解:Server 层、SQL 执行流程与 InnoDB、MyISAM、MEMORY 存储引擎》
sql·mysql·架构
这个DBA有点耶2 天前
SQL:2023新特性详解:JSON_TABLE、GREATEST/LEAST、QUALIFY 到底怎么用?
数据库·sql·mysql
小小龙学IT2 天前
Qt 数据库编程(Qt SQL 模块)深度解析
数据库·sql·qt
疯狂打码的少年2 天前
【数据库技术】SQL概述与数据定义(DDL:CREATE/DROP/ALTER)
数据库·笔记·sql·oracle
Blockchina2 天前
Codex安全盲区:我用4组本地测试复盘SQLi、XSS、越权与路径穿越
sql·安全·xss
山峰哥3 天前
数据库工程与查询优化案例深度复盘‌
数据库·sql·oracle·编辑器·深度优先·宽度优先
茶栀(*´I`*)3 天前
数据库零基础入门指南:从基本概念到 SQL 实战
数据库·sql