mysql 5.7+派生表合并需同时满足:无聚合函数、无distinct/limit/order by、无union、不超过61张基表;否则强制物化,explain中select_type为derived即未合并。

派生表合并的触发条件
MySQL 5.7+ 默认开启 derived_merge=on,但是否真能合并,不取决于开关本身,而取决于子查询结构是否“可合并”。只要满足以下全部条件,优化器才会把 FROM 中的子查询(即派生表)直接展开进外层查询,避免物化临时表。
- 子查询不含聚合函数(
GROUP BY、SUM、COUNT等) - 子查询不含
DISTINCT、LIMIT、OFFSET - 子查询不含
ORDER BY(除非外层无ORDER BY且是唯一数据源,此时可能传播,但不保证合并) - 子查询不包含窗口函数(MySQL 8.0+)
- 合并后不会导致外层查询引用超过 61 张基础表(这是硬限制,超了强制物化)
为什么有些看似简单的子查询也没被合并
常见误判点:以为 “没写 GROUP BY 就一定能合并”,其实还有隐藏障碍。比如:
-
SELECT * FROM (SELECT a, b FROM t1 WHERE c > 10) AS dt JOIN t2 ON dt.a = t2.x—— 这个通常能合并 -
SELECT * FROM (SELECT a, b FROM t1 ORDER BY a) AS dt JOIN t2 ON dt.a = t2.x——ORDER BY阻止合并,哪怕外层没排序需求 -
SELECT * FROM (SELECT a, b FROM t1 UNION SELECT a, b FROM t3) AS dt——UNION视为不可合并结构,强制物化 -
SELECT * FROM (SELECT * FROM t1) AS dt WHERE dt.a IN (SELECT x FROM t4)—— 外层有相关子查询时,合并可能被抑制(尤其当相关性影响执行计划稳定性)
如何验证是否发生了合并
最直接的方式是看 EXPLAIN 输出中的 select_type 字段:
- 如果看到
DERIVED,说明该子查询被物化为临时表(没合并) - 如果子查询消失,只看到
PRIMARY和SIMPLE类型,且表名直接列出原始基表(如t1、t2),说明已合并 - 配合
EXPLAIN FORMAT=TREE(MySQL 8.0.16+)能更清晰看到“merged derived table”字样
合并失败时的典型性能影响
物化派生表不是绝对坏,但容易在几个地方踩坑:
- 大数据量下,物化过程会走磁盘临时表(
Using temporary),I/O 开销陡增 - 物化后的临时表默认无索引,后续
JOIN或WHERE条件无法利用原表索引 - 如果外层有
WHERE条件(如WHERE dt.id = 123),MySQL 8.0.22+ 支持“派生条件下推”,但前提是没被合并——也就是说,**合并和条件下推是互斥策略**;你得根据场景权衡:要减少中间结果集大小(选物化+条件下推),还是要消除临时表开销(选合并)
真正难处理的是那种“看起来该合并却没合并”的情况——往往卡在某个隐式限制上,比如嵌套层级、UNION ALL 的 presence、或 optimizer_switch 里其他标志(如 subquery_to_derived)的副作用。这时候别只盯着 derived_merge,先用 EXPLAIN 定位,再逐项排除结构约束。











