订阅更新

SQL 相关子查询和普通子查询有什么区别?

面试问 154 约 2 分钟读完

本文目录
  1. 面试怎么回答
  2. 普通子查询:可以脱离外层独立求值
  3. 相关子查询:结果依赖当前外层行
  4. 优化器可能把它改写成 Join
  5. 还要关注返回基数和 NULL 语义
  6. 一个典型场景

面试怎么回答

普通子查询不引用外层列,可以独立求值;相关子查询引用外层当前行,逻辑上存在逐行依赖。但相关子查询不一定真的执行 N 次,优化器可能把 EXISTS、等值相关子查询改写成 Semi Join、聚合 Join 等计划。我还会检查标量子查询的单值约束、NULL 语义和改写后的行数基数,最终通过执行计划确认真实执行方式。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

面试官如果继续追问:“子查询到底执行几次?”

只回答一次或 N 次都不严谨,因为逻辑语义和物理执行不是一回事。

普通子查询和相关子查询的本质区别,是子查询是否依赖当前外层行。至于最终执行几次,要看优化器怎么改写。


普通子查询:可以脱离外层独立求值

如果子查询没有引用外层查询的列,它就是非相关子查询。

例如先求全表平均金额,再筛选高于平均值的订单。这个平均值在逻辑上与某一条外层订单无关,优化器可以先计算、物化或广播结果。

但“可以独立求值”不等于所有引擎一定只执行一次。CTE 是否内联、子查询是否复用,仍要看引擎版本和执行计划。

相关子查询:结果依赖当前外层行

相关子查询会引用外层列,例如:查询每个部门中工资高于本部门平均值的员工。

逻辑上,每个员工都需要对应自己部门的平均工资,所以它与当前外层行相关。

很多人因此直接得出“相关子查询一定逐行执行”的结论。这个说法只描述了最朴素的执行方式,没有考虑优化器的去相关能力。

优化器可能把它改写成 Join

对于 EXISTS、IN、等值相关条件,优化器常能改写为 Semi Join、Anti Join、聚合后 Join 等物理计划。

真正难优化的通常是复杂非等值条件、带顺序依赖的 LIMIT、包含不稳定函数,或者无法证明单值约束的标量子查询。

普通与相关子查询对比

还要关注返回基数和 NULL 语义

标量子查询要求最多返回一行。数据不满足唯一性时,有的引擎会直接报错,而不是“随便取一条”。

EXISTS 关心是否存在,不关心返回列;NOT IN 则会受到 NULL 的三值逻辑影响。选择写法时,不能只比较性能,还要先保证语义一致。

一个典型场景

一条报表 SQL 在小数据量测试正常,上线后变慢。开发看到相关子查询就直接改成 JOIN,结果因为维表键不唯一,把事实表行数放大了。

正确做法是先确认子查询的单值约束,再把维表聚合或去重到目标粒度,最后比较改写前后的执行计划和结果行数。

SQL 改写不是换语法,而是保持语义、约束和基数不变。

刷题进度保存在当前浏览器