一、SQL优化思路
本次优化基于业务高峰期 AWR 性能报告分析,提取执行时长、逻辑读、DB CPU消耗均处于 TOP 的共性业务 SQL,选取 1 条典型慢 SQL 作为优化对象。这些 SQL 普遍存在大表全表扫描、索引失效、冗余排序、重复子查询等问题,严重占用数据库资源,影响业务响应速度。本次优化遵循先 SQL 改写、再索引优化、最后验证效果的思路,统一规范、集中治理。
二、SQL优化内容
SQL ID:d5g14jqk1ujm7
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| SELECT DECODE(:1, 'buyerNo', t2.buyerno, 'bankNo', t2.bankno) AS mainNo, :2 AS mainnoType, t2.renewalendorseno AS renewalEndorseNo, t2.actualratio AS actualRatio FROM ( -- 行号排序层:按buyerNo/bankNo分区,取最新的renewalendorseno SELECT t1.buyerno, t1.bankno, t1.renewalendorseno, t1.actualratio, ROW_NUMBER() OVER( PARTITION BY DECODE(:3, 'buyerNo', t1.buyerno, 'bankNo', t1.bankno) ORDER BY t1.renewalendorseno DESC NULLS LAST ) AS rk FROM ( -- 数据合并层:4张明细表UNION拼接 SELECT DISTINCT t.buyerno, t.bankno, t.renewalendorseno, t.actualratio FROM c1.t_TABLE_A t WHERE DECODE(:4, 'buyerNo', t.buyerno, 'bankNo', t.bankno) = :5 UNION SELECT DISTINCT t.buyerno, t.bankno, t.renewalendorseno, t.actualratio FROM c1.t_TABLE_C t WHERE DECODE(:6, 'buyerNo', t.buyerno, 'bankNo', t.bankno) = :7 UNION SELECT DISTINCT t.buyerno, t.bankno, t.renewalendorseno, t.actualratio FROM c1.t_TABLE_D t WHERE DECODE(:8, 'buyerNo', t.buyerno, 'bankNo', t.bankno) = :9 UNION SELECT DISTINCT t.buyerno, t.bankno, t.renewalendorseno, t.actualratio FROM c1.t_TABLE_B t WHERE t.recontractno LIKE 'STQS%' AND DECODE(:10, 'buyerNo', t.buyerno, 'bankNo', t.bankno) = :11 ) t1 ) t2 WHERE t2.rk = 1 AND ROWNUM = 1; |
1. SQL语句相关信息收集
执行计划
|----|-------------------------|-----------|-------|---------|------------|----------|
| Id | Operation | Name | Bytes | TempSpc | Cost (CPU) | Time |
| 0 | SELECT STATEMENT | | | | 2419K(100) | |
| 1 | COUNT STOPKEY | | | | | |
| 2 | VIEW | | 111M | | 2419K(1) | 00:01:35 |
| 3 | WINDOW SORT PUSHED RANK | | 93M | 113M | 2419K(1) | 00:01:35 |
| 4 | VIEW | | 93M | | 2395K(1) | 00:01:34 |
| 5 | SORT UNIQUE | | 85M | 62M | 2395K(1) | 00:01:34 |
| 6 | UNION-ALL | | | | | |
| 7 | TABLE ACCESS FULL | T_TABLE_A | 39M | | 2278K(1) | 00:01:30 |
| 8 | TABLE ACCESS FULL | T_TABLE_C | 279K | | 6767(1) | 00:00:01 |
| 9 | TABLE ACCESS FULL | T_TABLE_D | 241K | | 5960(1) | 00:00:01 |
| 10 | TABLE ACCESS FULL | T_TABLE_B | 2709K | | 92216(1) | 00:00:04 |
| Plan hash value:3367240835 |||||||
表的统计信息收集情况
|-------|------------|-----------|---------|--------------|---------------------|
| OWNER | TABLE NAME | NUM ROWS | BLOCKS | EMPTY BLOCKS | LAST ANALYZED |
| C1 | T_TABLE_C | 923093 | 24898 | 0 | 2026-02-25 23:01:58 |
| C1 | T_TABLE_A | 138937071 | 8394885 | 0 | 2026-02-25 23:35:19 |
| C1 | T_TABLE_B | 7902351 | 339703 | 0 | 2026-02-25 23:38:12 |
| C1 | T_TABLE_D | 798490 | 21936 | 0 | 2026-02-25 23:36:59 |
表的实际行数
|--------------|-----------|
| 表名 | COUNT(*) |
| C1.T_TABLE_A | 140346456 |
| C1.T_TABLE_C | 932003 |
| C1.T_TABLE_D | 804046 |
| C1.T_TABLE_B | 8100094 |
索引创建情况
|--------|-----------------------------------------------------------|
| 索引相关信息 | 内容 |
| 索引基础信息 | 存在多表的单列索引,如IDX_TABLE_A_BUYER、IDX_TABLE_A_BANK等,无针对性复合覆盖索引 |
2. 当前语句性能分析
(1)执行计划核心问题
- 全表扫描(TABLE ACCESS FULL):4 张表均未走索引,尤其是 T_TABLE_A(约 1.4 亿行)全表扫描,是性能瓶颈。
- 大量排序与去重:UNION + SELECT DISTINCT + ROW_NUMBER() OVER() + SORT UNIQUE 导致多次内存 / 临时表排序,CPU 和 IO 消耗极高。
- 绑定变量导致的索引失效:DECODE(:N, 'buyerNo', t.buyerno, 'bankNo', t.bankno) = :M 这种写法会让优化器无法确定过滤列,导致索引无法被利用。
- 数据量巨大:
- T_TABLE_A:约 1.4 亿行
- T_TABLE_B:约 810 万行
- T_TABLE_C:约 93 万行
- T_TABLE_D:约 80 万行
(2)索引与统计信息问题
- 虽然存在单字段索引(如 IDX_TABLE_A_BUYER、IDX_TABLE_A_BANK),但因 DECODE 函数包裹列,索引无法被命中。
- 统计信息较新(2026-02-25),无需重新收集统计信息。
- 存在冗余索引(如 T_TABLE_A 上多个单列索引),但未针对查询条件创建覆盖索引。
3. 优化方案
方案 1:改写 SQL,消除函数导致的索引失效
将 DECODE 条件拆分为静态分支,让优化器能识别过滤列并走索引:
- 原写法(索引失效):WHERE DECODE(:N, 'buyerNo', t.buyerno, 'bankNo', t.bankno) = :M
- 优化后写法(索引生效):WHERE (:N = 'buyerNo' AND t.buyerno = :M) OR (:N = 'bankNo' AND t.bankno = :M)
方案 2:创建覆盖索引(Covering Index)
针对 4 张表的查询条件和返回列,创建复合覆盖索引,避免回表(TABLE ACCESS BY INDEX ROWID):
(1)表 T_TABLE_A
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| -- buyerNo 分支覆盖索引 CREATE INDEX IDX_T_TABLE_A_BUYER_COV ON T_TABLE_A (buyerno, renewalendorseno DESC) INCLUDE (bankno, actualratio) TABLESPACE ...; -- bankNo 分支覆盖索引 CREATE INDEX IDX_T_TABLE_A_BANK_COV ON T_TABLE_A (bankno, renewalendorseno DESC) INCLUDE (buyerno, actualratio) TABLESPACE ...; |
(2)表 T_TABLE_C
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| CREATE INDEX IDX_T_TABLE_C_BUYER_COV ON T_TABLE_C (buyerno, renewalendorseno DESC) INCLUDE (bankno, actualratio) TABLESPACE ...; CREATE INDEX IDX_T_TABLE_C_BANK_COV ON T_TABLE_C (bankno, renewalendorseno DESC) INCLUDE (buyerno, actualratio) TABLESPACE ...; |
(3)表 T_TABLE_D
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| CREATE INDEX IDX_T_TABLE_D_BUYER_COV ON T_TABLE_D (buyerno, renewalendorseno DESC) INCLUDE (bankno, actualratio) TABLESPACE ...; CREATE INDEX IDX_T_TABLE_D_BANK_COV ON T_TABLE_D (bankno, renewalendorseno DESC) INCLUDE (buyerno, actualratio) TABLESPACE ...; |
(4)表 T_TABLE_B(额外过滤条件 t.recontractno LIKE 'STQS%',需放入索引前缀)
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| CREATE INDEX IDX_T_TABLE_B_BUYER_COV ON T_TABLE_B (recontractno, buyerno, renewalendorseno DESC) INCLUDE (bankno, actualratio) TABLESPACE ...; CREATE INDEX IDX_T_TABLE_B_BANK_COV ON T_TABLE_B (recontractno, bankno, renewalendorseno DESC) INCLUDE (buyerno, actualratio) TABLESPACE ...; |
方案 3:简化 SQL 逻辑,消除冗余操作
(1)消除 SELECT DISTINCT:业务逻辑是取每个 buyerno/bankno 下最新的 renewalendorseno,UNION 已天然去重,且 ROW_NUMBER() 会分组取第一条,因此 SELECT DISTINCT 完全冗余,可直接删除。
(2)简化 ROWNUM = 1 逻辑:最终只取 1 条数据,可在窗口函数中直接限制,或在最外层提前终止。
4. 预期优化效果
|--------------------|-------------------------------------|
| 优化点 | 预期收益 |
| 消除 DECODE 函数,让索引生效 | 4 张表从全表扫描变为索引范围扫描 / 覆盖扫描,IO 降低 90%+ |
| 删除 SELECT DISTINCT | 减少内存排序,降低 CPU 和临时表使用 |
| 创建覆盖索引 | 避免回表,单次查询逻辑读降低 |
| 窗口函数优化 | 排序数据量从全表缩小到单 buyerno/bankno 分组 |
5. 实施注意事项
- SQL改写:未使用现有索引,优先建议SQL改写使现有索引生效。
- 索引创建时机:建议在业务低峰期创建,避免锁表影响业务。
- 索引维护:大表索引会增加 DML 操作开销,需评估读写比例。
- 测试验证:先在测试环境验证优化后 SQL 的执行计划和性能,再上线。
6. 最终执行计划预期
优化后执行计划应变为:
- 4 张表均走索引覆盖扫描(INDEX RANGE SCAN + INDEX FULL SCAN)
- 无 TABLE ACCESS FULL
- 窗口函数排序数据量极小
7. 补充内容
用 UNION ALL 替代 UNION:4张表数据来源不同,如果从业务侧可以确认不存在重复数据,用 UNION ALL 替代 UNION,可以消除 SORT UNIQUE 操作,大幅降低 CPU 消耗。