标量子查询在Oracle 19c中默认可能被自动解嵌套,但含聚合、函数或NULL敏感逻辑时优化器常跳过转换,导致N次重复执行;_optimizer_unnest_scalar_sq=FALSE可避免混合计划,强制手动重写。

标量子查询在Oracle 19c中默认可能被自动解嵌套,但不能依赖它——尤其当子查询含聚合、函数或NULL敏感逻辑时,优化器常选择不转换,导致N次重复执行。
为什么_optimizer_unnest_scalar_sq=FALSE是关键开关
Oracle 19c默认启用_optimizer_unnest_scalar_sq=TRUE,理论上会尝试将简单标量子查询转为JOIN。但实际中,只要子查询里出现以下任一情况,优化器就会跳过解嵌套:
-
SELECT列表中用了NVL、COALESCE或CASE包裹标量子查询 - 子查询WHERE条件含函数(如
TRIM(d.deptno)),导致索引失效 - 主表连接列(如
e.deptno)存在大量NULL值,且子查询未显式处理 - 子查询含
DISTINCT或ROWNUM等非确定性操作
此时强制设为FALSE反而更可控:避免优化器“半途而废”生成混合计划(既没完全解嵌套,又没走FILTER),直接引导你手动重写。
内联视图改写必须加GROUP BY防数据膨胀
把(SELECT MAX(e.sal) FROM emp e WHERE e.deptno = d.deptno)改成LEFT JOIN时,常见错误是直接LEFT JOIN emp e ON d.deptno = e.deptno后MAX(e.sal)——这会导致主表每行匹配多条员工记录,GROUP BY漏写就报ORA-00937。
正确做法分两步:
- 先在子查询内完成聚合:
(SELECT department_id, MAX(salary) AS max_sal FROM hr.employees GROUP BY department_id) - 再用该内联视图LEFT JOIN主表,且主查询的
SELECT和GROUP BY必须严格对齐(如主表选d.department_name,GROUP BY里也得有它)
漏掉内层GROUP BY,结果行数可能翻倍;漏掉外层GROUP BY,聚合函数直接报错。
LEFT JOIN后NVL位置决定NULL语义是否一致
原始标量子查询返回NULL时,NVL((SELECT ...), 0)能安全兜底。但改写成JOIN后,NVL(e.max_sal, 0)只对JOIN结果中的NULL生效——如果内联视图本身因GROUP BY缺失而没生成某部门记录,LEFT JOIN后该行e.max_sal仍是NULL,NVL仍起作用;但如果内联视图因过滤条件(如WHERE e.salary > 0)提前排除了所有记录,那该部门在内联视图中根本不存在,LEFT JOIN后同样为NULL,语义不变。
真正危险的是把NVL错放到内联视图里:SELECT department_id, NVL(MAX(salary), 0)...——这会让空部门返回0,而原标量子查询返回NULL,业务逻辑可能出错。
执行计划里找FILTER操作就是标量子查询的指纹
无论是否开启自动解嵌套,只要执行计划中出现FILTER操作符(尤其在DEPTH=1层级),且其子节点包含子查询表的全表扫描或索引范围扫描,基本可断定标量子查询未被转化。
对比两个计划:
- 未优化:主表
EMP扫描后接FILTER→ 对每行调用DEPT表扫描 - 已优化:
EMP与DEPT直接HASH JOIN或NESTED LOOPS,无FILTER
注意:即使看到VIEW节点,也不代表已解嵌套——可能是优化器生成的物化中间结果,仍带FILTER行为。盯紧OPERATION列最底层是否还有子查询表的独立访问路径。
标量子查询真正的复杂点不在语法,而在NULL传播路径和聚合边界——同一句SELECT里混用多个标量子查询时,每个都可能因连接列NULL值、子查询无结果、函数索引失效而走向不同执行分支,人工改写必须逐个验证结果集行数与NULL分布。











