or条件常致全表扫描,因优化器拆解分支时遇隐式转换、空值、低选择性等会降级路径;union all替代可稳定执行计划,需确保各支独立走索引且避免重复。

Oracle 存储过程中含 OR 条件的查询,十有八九会走全表扫描或执行计划崩坏——不是语法错,而是优化器在估算时直接放弃使用索引,尤其当 OR 两边涉及不同列、空值、函数或类型不一致时。
为什么 OR 条件会让执行计划失效?
Oracle 优化器对 OR 的处理逻辑是“拆解为多个分支再合并”,但前提是各分支能独立走索引。一旦某个分支触发隐式转换、IS NULL、NVL() 或选择性极低(比如 status IN ('A','B','C') 占全表 90%),整个条件就会被降级为低效路径。
-
WHERE col1 = :a OR col2 = :b:若col1和col2没有联合索引,且各自索引选择性不高,优化器大概率选全表扫描 -
WHERE NVL(status, 'X') = :s OR created_date > :dt:NVL()让status索引完全失效,且两个条件数据分布差异大,COST 估算严重失真 -
WHERE id = :x OR name LIKE :y || '%':LIKE前缀模糊 +=精确匹配混用,优化器无法统一驱动策略
用 UNION ALL 替代 OR 的实操要点
这是最稳定、最容易控制执行路径的方式。关键不是简单替换,而是确保每个分支都可独立走索引,并避免重复数据干扰。
- 每个
SELECT分支必须有明确的访问路径:比如WHERE col1 = :a走idx_col1,WHERE col2 = :b走idx_col2 - 用
UNION ALL而非UNION,除非业务真需要去重;UNION会额外触发排序和唯一性校验,开销翻倍 - 若原
OR条件含NULL判断(如col IS NULL OR col = :v),拆成两支时注意补全过滤逻辑:WHERE col IS NULL和WHERE col = :v AND col IS NOT NULL - 在存储过程里,可加
/*+ USE_CONCAT */提示强制优化器走UNION-ALL路径,但仅限 11gR2+ 且optimizer_features_enable≥ 11.2.0.4
哪些 OR 写法必须立刻改掉?
这些模式在存储过程中高频出现,但几乎必然导致性能雪崩,且很难靠统计信息或绑定变量提示修复:
-
WHERE col1 = :a OR col1 IS NULL:直接等价于WHERE NVL(col1, :a) = :a,索引彻底失效;应改为WHERE col1 = :a OR (:a IS NULL AND col1 IS NULL)并确保:a是IN参数 -
WHERE TO_CHAR(date_col, 'YYYYMM') = :ym OR date_col >= :dt:函数 + 非函数混用,优化器无法统一估算;应统一为范围:WHERE date_col >= ADD_MONTHS(TRUNC(:dt, 'MM'), -1) AND date_col -
WHERE status IN ('P','C') OR flag = 'Y':若status和flag都是低选择性字段,组合索引也难救;优先考虑分区裁剪或物化中间结果 -
WHERE id = :x OR UPPER(name) = UPPER(:n):大小写转换 + 精确匹配,应建函数索引CREATE INDEX idx_name_upper ON t (UPPER(name))并确保该分支单独可走索引
验证是否真绕过了 OR 的陷阱?
别信 EXPLAIN PLAN FOR,它不绑定变量、不模拟真实执行上下文:
- 在存储过程中加
DBMS_OUTPUT.PUT_LINE('SQL_ID: ' || sql_id)打印实际执行的sql_id,然后查V$SQL_PLAN看OPERATION是否含CONCATENATION或多个INDEX RANGE SCAN - 执行后立刻跑
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST')),重点看A-Rows(实际行数)与E-Rows(预估)是否接近;若某分支A-Rows = 0但STARTS = 1,说明裁剪生效 - 检查
PHYSICAL READS和BUFFER GETS是否显著下降——这才是OR改写是否成功的硬指标
最易被忽略的是:存储过程里参数传入后,OR 条件中某些分支在特定参数组合下根本不会执行,但优化器仍按“最差情况”生成计划;必须用真实参数值捕获 DISPLAY_CURSOR,而不是靠开发环境随便跑一遍就认为 OK。











