是的,Oracle从9i起默认启用标量子查询缓存,无需显式开启,由优化器自动识别应用;需同时满足标量性、驱动值重复、无不确定性函数、无非相关上下文四个条件才生效。

标量子查询缓存(Scalar Subquery Caching)在 Oracle 中默认开启吗?
是的,Oracle 从 9i 起就默认启用标量子查询缓存,**无需显式开启**。它不是靠参数开关控制的,而是由优化器自动识别并应用的运行时优化机制。只要满足缓存条件,同一标量子查询在单个 SQL 执行过程中对相同输入值的多次调用,就会复用前一次的结果,跳过重复执行。
常见误解是去查 optimizer_features_enable 或试图设置某个隐藏参数来“开启”,其实没必要——关键在于写法是否能让优化器识别出可缓存的模式。
哪些标量子查询能被缓存?必须满足的 4 个条件
缓存只在严格满足以下全部条件时生效,缺一不可:
- 子查询必须是**标量的**:返回 0 或 1 行、且仅 1 列(如
(SELECT dept_name FROM dept WHERE dept_id = e.dept_id)) - 外部查询中该子查询的**驱动列值在本次执行中重复出现**(例如表中有多个员工属于同一部门,
e.dept_id值重复) - 子查询本身**不包含不确定性函数**:禁止
SYSDATE、DBMS_RANDOM.VALUE、SYSTIMESTAMP、ROWNUM、用户自定义的非确定性函数等 - 子查询不能引用**外部查询的非相关列以外的上下文**:比如不能依赖
OUTER JOIN的空值传播逻辑或WITH子句中的变量(除非该WITH是内联且无副作用)
为什么加了索引还是没看到缓存效果?常见失效场景
即使语法合规,缓存也可能静默失效,典型原因包括:
-
NULL值处理:如果驱动列为NULL,Oracle 将其视为一个独立“值”,但某些版本对NULL的缓存键处理不一致,建议在子查询中显式过滤或用NVL统一(如WHERE dept_id = NVL(e.dept_id, -1)) - 数据类型隐式转换:外部列是
VARCHAR2(10),而子查询谓词列是CHAR(10),会导致每次比较都触发新执行 —— 检查执行计划中是否有TO_CHAR或TO_NUMBER自动转换 - 绑定变量未复用:使用不同字面量(如
WHERE dept_id = 10和WHERE dept_id = 10看似一样,但若一次用字符串'10'一次用数字10,会被视为两个键) - 子查询含
ORDER BY或FETCH FIRST:即使只取一行,这些子句会阻止缓存(优化器无法保证结果稳定性)
如何验证缓存是否实际生效?看 v$sql_plan_statistics_all 和 trace
最直接的方式是查执行统计,而非仅看执行计划:
先执行目标 SQL,再查:
SELECT sql_id, child_number, operation, options,
starts, buffers, cpu_time, elapsed_time
FROM v$sql_plan_statistics_all
WHERE sql_id = '&your_sql_id'
AND operation = 'SCALAR SUBQUERY';
重点看 starts 字段:如果驱动行有 1000 行、但 starts = 50,说明缓存命中约 95%;若 starts ≈ 驱动行数,则基本没缓存。
更底层可开 10046 trace:ALTER SESSION SET EVENTS '10046 trace name context forever, level 8';,然后在 trace 文件里搜索 EXEC # 出现次数 —— 对同一个子查询地址,只应出现几次 EXEC,后续都是 FETCH 或直接跳过。
注意:缓存只存在于单次 SQL 执行生命周期内,跨语句、跨会话不共享。别指望它替代物化视图或结果集缓存。


















