导读:业务视图里常见一种写法——为了把某个字段的不同取值映射成不同的「页面类型」,写了 N 个 UNION ALL 分支,每个分支
WHERE usage_detail_code='C0X',各自输出一个page_type字面量。表一大(几百万行)这种写法就特别慢:每个分支全表扫一次,N 个分支就是 N 次全表扫。本文用 600 万行的实测数据,演示如何把它等价改写为IN+DECODE,让全表扫描次数从 11 次降到 1 次。
先看这种写法的原始面貌。一个视图为了给字段 usage_detail_code 的不同取值映射成不同「页面类型」,写了 11 个 UNION ALL 分支:
CREATE OR REPLACE VIEW V_USAGE_DETAIL AS
SELECT u.USAGE_ID, u.BILL_ID, u.AMOUNT, u.USE_TIME, 'P1' AS PAGE_TYPE
FROM USAGE_RECORD u WHERE u.USAGE_DETAIL_CODE = 'C01'
UNION ALL
SELECT u.USAGE_ID, u.BILL_ID, u.AMOUNT, u.USE_TIME, 'P2' AS PAGE_TYPE
FROM USAGE_RECORD u WHERE u.USAGE_DETAIL_CODE = 'C02'
UNION ALL
-- ...(中间省略 C03 ~ C10)
SELECT u.USAGE_ID, u.BILL_ID, u.AMOUNT, u.USE_TIME, 'P11' AS PAGE_TYPE
FROM USAGE_RECORD u WHERE u.USAGE_DETAIL_CODE = 'C11';
问题就出在这 11 个分支上:UNION ALL 的每一个分支,都是对 USAGE_RECORD 表的一次独立扫描。11 个分支就是 11 次全表扫,最后再 UNION ALL 合并,代价成倍放大。
观察这 11 个分支,可以发现它们的规律性极强:
WHERE usage_detail_code='C0X' 只是取值不同;SELECT 'PX' page_type 只是字面量映射不同。于是可以两步收口:
WHERE ... = 'C0X' 合并成 一个 IN (...11 个值...);SELECT 'PX' 字面量,用 DECODE(usage_detail_code, ...) 函数映射。改写后的视图:
CREATE OR REPLACE VIEW V_USAGE_DETAIL_NEW AS
SELECT u.USAGE_ID, u.BILL_ID, u.AMOUNT, u.USE_TIME,
DECODE(u.USAGE_DETAIL_CODE,
'C01','P1', 'C02','P2', 'C03','P3', 'C04','P4', 'C05','P5',
'C06','P6', 'C07','P7', 'C08','P8', 'C09','P9', 'C10','P10',
'C11','P11', 'UNKNOWN') AS PAGE_TYPE
FROM USAGE_RECORD u
WHERE u.USAGE_DETAIL_CODE IN ('C01','C02','C03','C04','C05',
'C06','C07','C08','C09','C10','C11');
逻辑完全等价:原来按 code 分流输出不同 page_type,改写后 IN 一次取全量、DECODE 按 code 映射同一 page_type。DECODE 找不到匹配时返回默认值 'UNKNOWN'(原写法里不在 C01~C11 范围内的行本来就不会被取到,加默认值是为保险起见)。
测试环境:达梦 DM8(db1 / 端口 5236),USAGE_RECORD 表 600 万行。
| 校验项 | 原版(11 UNION ALL) | 改写版(IN + DECODE) | 是否一致 |
|---|---|---|---|
| 总行数 | 600 万 | 600 万 | ✅ |
业务 SQL 结果(视图 JOIN 账单维表 STATUS='V') |
540 万 | 540 万 | ✅ |
| 各 PAGE_TYPE 分组计数 | P1~P11 各约 54.5 万 | 与左完全一致 | ✅ |
| 方案 | 执行时间 | 提升 |
|---|---|---|
| 原版(11 UNION ALL) | 2.56 s | 基线 |
| 改写版(IN + DECODE) | 0.46 s | ↓ 5.6 倍 |
数据量再放大(几千万~上亿行)、分支再多时,差距会更大,因为原版耗时 ≈ 分支数 × 单次全表扫。
#NSET2
└ #HASH2 INNER JOIN (DIM_BILL)
└ #UNION ALL ← 10 层嵌套 UNION ALL
└ #UNION ALL
└ ... 共 11 个分支 ...
└ #SLCT2 U.USAGE_DETAIL_CODE='C01' → #CSCN2 USAGE_RECORD (扫 600 万)
└ #SLCT2 U.USAGE_DETAIL_CODE='C02' → #CSCN2 USAGE_RECORD (扫 600 万)
└ ...(共 11 个 #CSCN2,每次全扫 600 万)
#NSET2
└ #HASH RIGHT SEMI JOIN2 (USAGE_DETAIL_CODE = 值列表)
├ #CONST VALUE LIST: row_num(11) ← 11 个 code 常量化成值列表
└ #HASH2 INNER JOIN (DIM_BILL)
└ #SLCT2 BILL_STATUS='V'
└ #CSCN2 USAGE_RECORD (只扫 1 次 600 万)
关键差异:
#CSCN2 全扫 → 扫 11 次,每次 600 万行;IN (...) 被优化成 #CONST VALUE LIST(11) + 半连接,只扫 1 次,DECODE 在投影层完成 page_type 映射。DECODE 是达梦兼容 Oracle 的条件取值函数,等价于 CASE WHEN 的简写形式:
-- DECODE 写法
DECODE(expr, 值1, 结果1, 值2, 结果2, ..., 默认值)
-- 等价于 CASE 写法
CASE expr WHEN 值1 THEN 结果1
WHEN 值2 THEN 结果2
...
ELSE 默认值 END
在这个场景里,用 DECODE 或 CASE WHEN 都能达到同样效果,DECODE 更简洁。
usage_detail_code 上有索引,改写后还能走索引范围扫描进一步加速;本例为突出 UNION ALL 的代价,故意不建索引做全表扫对比,效果更直观。文章
阅读量
获赞
