oracle 19c子查询展开默认更激进,sql server 2019则更保守;前者常自动转为semi/anti join并带vw_标识,后者多保留嵌套循环或apply,展开与否高度依赖统计信息准确性。

Oracle 19c 的子查询展开默认更激进
Oracle 19c 优化器在多数情况下会自动将非相关子查询(如 WHERE col IN (SELECT ...) 或 WHERE EXISTS (SELECT ...))展开为连接(JOIN),前提是语义等价且不破坏结果集。这种展开由 _optimizer_subquery_pruning_enabled 和 _optimizer_squ_bottomup 等隐含参数控制,默认开启。
典型触发场景包括:
-
IN子查询中内表有主键或唯一约束,且外层无重复值时,常转为NESTED LOOPS SEMI或HASH SEMI JOIN -
EXISTS子查询被推入驱动表后,可能合并为ANTI JOIN或SEMI JOIN - 视图定义中的子查询,在启用
VIEW_MERGE提示或满足合并条件时直接内联
但注意:若子查询含 ROWNUM、聚合 + GROUP BY 无确定性排序,或引用了外部 PL/SQL 函数,优化器会放弃展开,保留嵌套执行路径。
SQL Server 2019 的子查询展开更保守,依赖代价估算
SQL Server 2019 默认对子查询的展开较谨慎,尤其对 IN 和标量子查询。它更倾向保留嵌套循环(NESTED LOOPS)或使用 APPLY 算子(CROSS APPLY/OUTER APPLY)模拟逻辑,而非强制转为等价 JOIN。
常见表现:
-
WHERE col IN (SELECT id FROM t2)多数情况生成INNER JOIN,但若t2.id无索引或统计信息陈旧,可能退化为嵌套循环 + 哈希匹配,甚至警告“Warning: Null value is eliminated by an aggregate or other SET operation” - 标量子查询(如
SELECT (SELECT name FROM t2 WHERE t2.id = t1.ref) FROM t1)极少被展开,通常走APPLY或独立嵌套查找;若t2.id缺失索引,性能断崖式下跌 - 即使手动加
OPTION(RECOMPILE),也不保证展开——是否展开取决于基数估算是否认为 JOIN 更优
执行计划里怎么一眼识别是否展开了
看执行计划中最外层算子及子树结构:
- Oracle:出现
NESTED LOOPS SEMI、HASH JOIN SEMI、MERGE JOIN ANTI,或VW_SQ_1类型的视图别名(表示已重写为内联视图),说明已展开 - SQL Server:看到
Index Seek/Clustered Index Seek下挂Compute Scalar+Top,大概率仍是嵌套子查询;若出现Hash Match (Inner Join)或Loop Join且右侧输入明确是子查询表,则已展开 - 关键区别点:
SQL Server 执行计划里没有 “VW_” 前缀的改写标识,而 Oracle 的 <code>VW_ST_或VW_SQ_是展开的铁证
你改写时真正该关心的三个点
别只盯着“有没有展开”,要看实际效果:
- Oracle 中强行禁用展开(如加
NO_UNNEST)可能导致FULL TABLE SCAN,尤其当子查询过滤性强但未走索引时 - SQL Server 中用
CROSS APPLY显式替代IN子查询,有时比依赖自动展开更可控——特别是子查询需复用外层计算列时 - 两边都怕“相关子查询”:Oracle 的
WHERE col = (SELECT MAX(x) FROM t2 WHERE t2.id = t1.id)和 SQL Server 的等价写法,若t2.id无索引,都会变成每行一次独立查找,性能雪崩
最易被忽略的是统计信息时效性:Oracle 19c 展开决策严重依赖 DBMS_STATS 收集的列直方图;SQL Server 2019 则依赖自动更新的统计对象,过期统计会让展开判断彻底失准——这时看执行计划也没用,得先 UPDATE STATISTICS 或 DBMS_STATS.GATHER_TABLE_STATS。











