物化最直接的判断依据是EXPLAIN的select_type和Extra字段:select_type为DERIVED且table列为<derivedN>,或Extra含Start/End materialize;MySQL 8.0.16+推荐用EXPLAIN FORMAT=TREE查看MATERIALIZE节点。

看 EXPLAIN 的 select_type 和 Extra 字段
物化最直接的判断依据是执行计划中的 select_type 和 Extra。只要子查询被物化,select_type 会显示为 DERIVED(派生表),且外层查询的 table 列会显示为 <derivedN>(如 <derived2>)。
同时注意 Extra 列:MySQL 8.0.22+ 中若出现 Start materialize 或 End materialize,就是明确的物化标记;老版本则依赖 Using temporary(需结合上下文判断,它也可能是排序或 GROUP BY 引起的)。
-
select_type = DERIVED是物化的强信号,但仅适用于 FROM 子句中的子查询(即派生表) - WHERE 中的 IN 子查询若被物化,
select_type通常仍是SUBQUERY,此时得靠EXPLAIN FORMAT=TREE查看是否有MATERIALIZE节点 - 如果看到
select_type = DEPENDENT SUBQUERY,基本可断定未物化——它代表每行都重跑一次子查询
用 EXPLAIN FORMAT=TREE 确认物化节点
MySQL 8.0.16+ 支持 EXPLAIN FORMAT=TREE,这是目前最可靠的物化验证方式。它会以树形结构展示执行逻辑,其中明确包含 MATERIALIZE 节点的,就是优化器实际走的物化路径。
例如执行:EXPLAIN FORMAT=TREE SELECT * FROM t1 WHERE id IN (SELECT id FROM t2 WHERE status = 'active');,若输出中出现类似:
-> Materialize with deduplication (cost=1.15 rows=1)
-> Filter: (t2.status = 'active') (cost=1.15 rows=1)
-> Table scan on t2 (cost=1.05 rows=1)就说明物化已生效。反之,若看到 dependent subquery 节点,且其下有 Using temporary; Using filesort,说明物化失败,退化为嵌套循环。
检查子查询是否满足物化前提条件
物化不是“想物化就能物化”,它有一套硬性约束。哪怕你用的是 MySQL 8.0+,只要子查询违反以下任一条件,优化器就会放弃物化:
- 引用了外层列(如
t1.id = t2.user_id),即相关子查询 → 必然DEPENDENT SUBQUERY - 含聚合函数(
COUNT()、GROUP BY)、窗口函数、LIMIT、ORDER BY(哪怕只是ORDER BY RAND()) - 子查询本身含
UNION、用户变量、或非确定性函数(如NOW()) - 子查询结果集过大,超出
tmp_table_size,导致落盘后 B+ 树索引查找变慢,优化器可能主动绕过物化
特别注意:IN 和 EXISTS 语义不同,不能互相替换来“触发”物化。只有语义等价且无相关性时,优化器才可能重写;一旦含外层列,换写法也没用。
对比物化与半连接的实际执行开销
物化不是银弹。当子查询结果极小(比如几十行),而外层表极大(千万级)时,物化临时表的创建、拷贝、索引构建开销,可能比直接走 semi-join + 索引查找更重。
你可以用优化器提示临时禁用物化验证效果:SELECT * FROM t1 WHERE t1.id IN (SELECT /*+ NO_MATERIALIZATION() */ id FROM t2 WHERE status = 'active');,再看 EXPLAIN 是否变成 select_type = PRIMARY + Extra = Using join buffer (hash join)。如果性能反而提升,说明当前场景下物化反而是瓶颈。
真正容易被忽略的点是:物化表默认不建索引(除非极小结果集自动加哈希索引),一旦落盘就只能靠 B+ 树顺序扫描匹配——这时候 IN 判断效率可能还不如直接走外层索引去 probe 子查询表。


















