explain format=tree是mysql 8.0+唯一能直接观察子查询是否被重写为半连接、物化或join的手段,它显示重写后的逻辑结构而非原始嵌套形态;其中“-> materialize”表示物化,“-> nested loop”后跟两表名说明转为半连接,“filter”下仍见“in (subquery)”则重写失败;必须用format=tree,普通explain仅显示最终执行计划。

用EXPLAIN FORMAT=TREE看子查询重写结果
MySQL 8.0+ 的 EXPLAIN FORMAT=TREE 是唯一能直接观察优化器是否把子查询转成半连接(semi-join)、物化表或JOIN的手段。它会显示重写后的逻辑结构,而不是原始SQL的嵌套形态。
常见现象包括:-> Materialize 表示子查询被物化,-> Nested loop 后跟两个表名说明已转为半连接,-> Filter 下出现 IN (subquery) 未变化则大概率没重写成功。
执行时注意:必须加 FORMAT=TREE,普通 EXPLAIN 只显示最终执行计划,看不到中间重写步骤。
检查SELECT_TYPE识别依赖关系
EXPLAIN 输出的 select_type 列是判断子查询是否被重写的关键信号:
-
DEPENDENT SUBQUERY:子查询仍按传统方式执行,每行外层都触发一次——说明重写失败或被禁用 -
SUBQUERY:不相关子查询,通常会被物化或提前执行,但不保证已转JOIN -
DERIVED或MATERIALIZED:子查询被提取为派生表或物化表,属于重写成功的第一步 - 完全消失、只显示
SIMPLE和多表JOIN:说明已被优化器彻底转为半连接或常规JOIN
注意:DEPENDENT SUBQUERY 出现即代表性能风险,不是“还没轮到优化”,而是优化器主动放弃重写——往往因子查询含不确定函数(如 NOW()、RAND())或引用了外层未索引列。
对比optimizer_trace确认重写动作
启用 optimizer_trace 可看到优化器内部决策链,尤其关注 steps 数组中是否出现 transformations_to_nested_joins 或 materialization 字段:
操作步骤:
— 执行 SET optimizer_trace="enabled=on,one_line=off";
— 运行目标查询
— 查询 SELECT * FROM information_schema.OPTIMIZER_TRACE;
关键线索:
-
"transformation": "IN_TO_EXISTS"表示尝试转IN为EXISTS -
"transformation": "IN_TO_SEMIJOIN"表示启用半连接优化 -
"cause": "not applicable"紧跟在某个 transformation 后,说明该规则被跳过——要查具体原因(比如子查询含GROUP BY或窗口函数)
这个 trace 不是日志,而是一次性快照,每次查询后需手动清空或重开会话,否则可能看到上一条查询的残留信息。
为什么有些子查询死活不重写
重写失败不是随机的,基本由三类硬限制导致:
— 子查询里用了无法下推的表达式:DATE(login_time)、CONCAT(a,b)、IF() 等会让优化器放弃物化或半连接
— 外层 WHERE 条件含跨表引用,例如 WHERE t1.id IN (SELECT t2.ref_id FROM t2) AND t2.status = 'active' —— t2.status 在子查询外,优化器不敢擅自把它拉进子查询 WHERE
— 子查询本身不可确定:ORDER BY RAND()、LIMIT 无 ORDER BY、或含用户变量 @var,这些都会让优化器标记为“不可重写”
真正难处理的从来不是语法对不对,而是你写的那个子查询,到底有没有给优化器留下可预测、可下推、可索引的确定性路径。











