MySQL子查询物化需满足非相关、无聚合排序等严苛条件;若EXPLAIN中select_type为DEPENDENT SUBQUERY且Extra含Using temporary/Using filesort,则未物化,每行外层数据都会重复执行子查询。

看 EXPLAIN 的 select_type 和 Extra 字段
最直接的办法是执行 EXPLAIN FORMAT=TREE(MySQL 8.0.16+)或 EXPLAIN(通用),重点盯两个地方:select_type 列和 Extra 列。
非相关子查询被物化时,select_type 通常是 SUBQUERY(不是 DEPENDENT SUBQUERY),且 Extra 中会出现:
-
Start materialize(MySQL 8.0.22+) -
Using temporary(老版本,但注意:它也可能表示其他临时表用途,需结合上下文) -
Materialize节点包裹在->箭头下(FORMAT=TREE输出中)
如果看到 select_type = DEPENDENT SUBQUERY,基本可断定没物化——它会在外层每行触发一次重执行。
单独执行子查询不报错,大概率是非相关且可物化
物化只对非相关子查询生效。判断是否“非相关”,就一条硬标准:子查询里有没有引用外层表的列名,比如 t1.id、orders.user_id。
实操验证方法很简单:
- 把子查询整段复制出来,粘贴到新查询窗口,单独执行
SELECT—— 如果能跑通、不报Unknown column 'xxx' in 'field list',就是非相关 - 若子查询含
NOW()、RAND()、用户变量@var或LIMIT无ORDER BY,即使不报错,优化器也会拒绝物化 - 聚合函数如
AVG()、COUNT()本身不阻止物化,但若和GROUP BY或窗口函数混用,就会失效
PostgreSQL 里重点看 SubPlan 和 actual time
PostgreSQL 不叫“物化”,但行为类似。用 EXPLAIN (ANALYZE, VERBOSE) 观察:
- 如果子查询显示为
SubPlan,且actual time是稳定小值(如0.001..0.002 rows=1),且没有出现never executed,说明只执行了一次 - 若看到
CTE Scan on cte+ 前面紧跟Materialize节点,说明 CTE 被强制物化了(注意:CTE 默认就物化,和普通子查询不同) - 普通子查询(非 CTE)在无
ORDER BY/DISTINCT/GROUP BY时更可能被内联,此时执行计划里根本看不到独立子查询节点
别信“语法看起来像常量”就以为被物化
写成 WHERE status = (SELECT id FROM status_codes WHERE code = 'active') 看起来很安全,但以下情况会让物化失效:
-
status_codes.code列没索引 → 子查询全表扫描,优化器可能放弃物化路径 - 子查询返回多行 → 报错
Subquery returns more than 1 row,但某些旧版 MySQL 会静默取第一行,还绕过优化逻辑 - 用了
/*+ NO_MERGE() */这类优化器提示,人为阻断内联机会
真正决定是否物化的,从来不是你“觉得它该只算一次”,而是优化器在生成执行计划那一刻,基于统计信息、索引可用性、语义确定性做的综合判断——所以每次改数据分布或加索引后,都值得重新 EXPLAIN 一眼。

















