派生表默认易被物化为无索引临时表,导致join时全表扫描;mysql 5.7及更早版本强制物化,不满足合并条件(如无group by/distinct/limit/order by、无聚合、单数据源等)则无法下推条件或使用原表索引。

FROM子句里的派生表((SELECT ...))默认容易被强制物化,变成无索引的临时表,导致JOIN时全表扫描——这不是“写法优雅”的问题,而是性能雪崩的起点。
为什么派生表一用就慢?
MySQL 5.7 及更早版本对 FROM 中的子查询默认走物化路径:先执行完子查询、生成内部临时表、再参与外层逻辑。哪怕你只 SELECT id FROM (SELECT id FROM t WHERE status=1) AS dt,只要没满足合并条件,它就不会下推 WHERE,也不会用上 t.status 上的索引。
- 物化后临时表没有主键或索引(除非优化器自动加,但仅限于 JOIN 条件列)
- 外层
JOIN若以该派生表为被驱动表,每次匹配都要扫完整个临时结果集 -
UNION ALL多表拼接成的派生表(如日志分表汇总),会逐个全表扫描源表,叠加放大开销
怎么让派生表不物化?看合并条件是否全满足
MySQL 优先尝试合并(merge),也就是把子查询“打平”进外层,等价于直接 JOIN 原表。但必须同时满足:
- 外层
FROM中只有该派生表一个数据源,或只与简单表JOIN - 子查询不含
GROUP BY、DISTINCT、LIMIT、ORDER BY(除非外层也无排序需求) - 子查询不包含聚合函数(
COUNT()、SUM()等) - 整个查询涉及的表总数 ≤ 61(默认
optimizer_search_depth)
例如:SELECT * FROM (SELECT id, name FROM users WHERE deleted = 0) AS u WHERE u.id > 1000,若外层无 GROUP BY 且 users 表上有 deleted 和 id 的复合索引,就极可能合并成功。
MySQL 8.0.22+ 可以下推条件,但得主动触发
即使无法合并,8.0.22 起支持“派生条件下推”(Derived Condition Pushdown),把外层 WHERE 条件塞进子查询里执行。但它不会自动生效,需满足两个前提:
- 派生表不可合并(比如含
GROUP BY或UNION) - 外层
WHERE条件能安全下推(即不改变语义,如不能下推到含AGG的子查询的聚合层)
实操建议:
– 显式加提示:在子查询中用 /*+ DERIVED_CONDITION_PUSHDOWN(dt) */(dt 是别名)
– 验证是否生效:查 EXPLAIN FORMAT=TREE,看到 -> Filter on dt.i 类似字样才算成功
– 注意:下推后子查询本身变重了,但整体扫描行数大幅减少,尤其适合大派生表 + 强过滤场景
实在没法合并/下推?那就别用派生表
当派生表结构复杂(多 UNION ALL、嵌套聚合、多次引用),强行让它“工作”只会掩盖根本问题。更直接的解法是:
- 用
CREATE TEMPORARY TABLE显式建带索引的中间表,手动控制物化时机和索引策略 - 把
UNION ALL拆成多个INSERT INTO ... SELECT,并在临时表上建INDEX(taskname) - 将派生表逻辑前置为 CTE(MySQL 8.0+),如果它被多次引用,CTE 可复用;但单次引用时 CTE 不比派生表快
- 终极改写:把
FROM (SELECT ...) AS dt JOIN a ON a.x = dt.y改成a JOIN src_table ON ...,把子查询条件全部下推到原表ON或WHERE中
最常被忽略的一点:你以为加了 LIMIT 能减小中间集,但 MySQL 物化时往往先全量生成再截断——LIMIT 在派生表里基本不缓解压力,除非配合 ORDER BY 且你能确保合并发生。










