不相关子查询只执行一次,因为优化器能静态识别其不依赖外层表列,主动将其拎出单独计算并物化结果复用;证据见explain中materialize节点、subplan或独立view步骤,而now()、rand()、用户变量等会导致退化为重复执行。

不相关子查询为什么只执行一次?看优化器怎么“拎出来算”
因为数据库优化器能静态识别它不依赖外层表的任何列,于是主动把它从主查询逻辑中“拎出来”,单独执行一次,结果物化(materialize)后复用——这不是语法约定,而是真实发生的执行行为。
关键证据在 EXPLAIN 输出里:
- MySQL 8.0+ 用 EXPLAIN FORMAT=TREE,看到子查询被包在 -> Materialize 节点下,就是已物化
- PostgreSQL 用 EXPLAIN (ANALYZE, VERBOSE),若显示 SubPlan 且 actual time 极短、never executed 没出现,基本确认单次执行
- Oracle 中表现为独立的 VIEW 或 UNION ALL 步骤,而非嵌套循环
怎么快速判断一个子查询是不是不相关的?别猜,动手试
核心就一条:把子查询整段复制出来,单独运行,看能不能不报错、出结果。
- 能跑通 → 不相关,例如
(SELECT AVG(salary) FROM employees)、(SELECT value FROM config WHERE key = 'timeout') - 报
Unknown column 't1.id'或类似错误 → 相关,比如(SELECT COUNT(*) FROM logs WHERE user_id = t1.id)(哪怕只多写了个点号) - 注意陷阱:子查询里没显式引用外层列,但用了
NOW()、RAND()、用户变量@counter,也会被判定为“不可缓存”,强制重复执行
哪些写法会让不相关子查询“假相关”?隐蔽但致命
表面看不依赖外层,实际被优化器判为相关,导致每行重算——这是线上慢查最常踩的坑。
-
NOW()、RAND()、UUID()等不确定性函数:优化器认为结果不可复用,每次重求值 - 子查询含
LIMIT但没ORDER BY:MySQL 可能拒绝物化,因结果不稳定 - 误用外层别名却未实际引用:比如
SELECT * FROM employees e WHERE e.id IN (SELECT id FROM departments),虽然e.id没出现在子查询里,但别名污染可能干扰解析器 - 标量子查询返回多行:如
WHERE x = (SELECT a, b FROM t),旧版 MySQL 可能只取第一行,还绕过物化路径
物化结果太大反而拖慢?这时候别硬扛,该换就得换
只执行一次 ≠ 一定快。物化本身有成本,尤其当子查询返回百万级行时,内存/磁盘临时表构建会卡住。
- 先问自己:子查询真需要全量结果吗?能否加
WHERE缩小范围? - 主查询是否只是做
IN或=匹配?如果是,考虑用JOIN+DISTINCT或临时表预热 - 注意语义风险:把
WHERE status = (SELECT id FROM status_codes WHERE code = 'active')改成JOIN,可能引入NULL或重复匹配,逻辑已变 - 聚合类子查询(如
AVG(salary))改写为JOIN后再GROUP BY,容易放大中间结果集,尤其外层表大、字段无索引时
物化是优化动作,不是银弹;真正要盯住的,是执行计划里那个 Materialize 节点到底花了多少时间、占了多少内存——而不是只看“只执行了一次”这个结论。











