派生表性能问题源于中间结果集爆炸,典型表现为Using temporary/Using filesort或rows远超预期,主因是SELECT *、过滤未下推、分组键无索引;应精简字段、下推WHERE、确保GROUP BY字段有索引,并考虑EXISTS替代或临时表缓存。

JOIN派生表时中间结果集爆炸的典型现象
执行计划里出现 Using temporary; Using filesort,或者 rows 值远高于预期(比如预估 50 万行,实际扫描 800 万行),基本就是派生表没压住数据量。常见诱因不是语法写错,而是子查询里带了 *、没下推过滤条件、或分组键没索引支撑。
- 派生表里写
SELECT *→ 把所有字段拖进内存,JOIN 时每字段都参与比较和传输 - 子查询没加
WHERE时间/状态过滤 → 全表聚合后才 JOIN,白算百万行 -
GROUP BY字段(如user_id)在源表上无索引 → 聚合变全表扫描,无法利用排序优化
先聚合再JOIN:派生表必须只保留最小必要字段
派生表不是“把子查询套一层括号”就完事,关键在它输出什么。只要外层 JOIN 不需要的字段,一律不选。
- 只
SELECT分组键 + 聚合值,例如:SELECT user_id, COUNT(*) AS order_cnt, SUM(amount) AS total - 确保分组键(如
user_id)在源表上有索引,否则GROUP BY无法走索引排序,性能反降 - 时间类过滤必须下推到派生表内部,例如:
WHERE created_at >= '2026-07-01'写在子查询里,别等 JOIN 后再筛
示例对比:
❌ 慢(派生表含冗余字段+无过滤):
SELECT u.name, t.* FROM users u JOIN (SELECT * FROM orders GROUP BY user_id) t ON u.id = t.user_id✅ 快(字段精简+条件下推):
SELECT u.name, t.order_cnt, t.total FROM users u JOIN (SELECT user_id, COUNT(*) AS order_cnt, SUM(amount) AS total FROM orders WHERE status = 'paid' GROUP BY user_id) t ON u.id = t.user_id
LEFT JOIN + EXISTS 替代方案:当只需要判断存在性时
如果派生表目的只是“查用户有没有订单”,而不是取订单明细,LEFT JOIN 就是过度设计。
-
LEFT JOIN orders o ON u.id = o.user_id WHERE o.id IS NOT NULL实际触发全量连接再过滤,中间结果膨胀 - 改用
EXISTS:数据库可走半连接(semi-join),不生成匹配行,也不传字段 - 注意:若后续还要取
o.created_at或o.amount,就不能只靠EXISTS,得回到 JOIN,但此时必须确保orders(user_id)有索引
临时表缓存派生结果:稳定大结果集的可控解法
当派生表逻辑固定(如每日活跃用户统计)、且被多个查询反复引用,临时表比依赖优化器更可靠。
- 显式建
CREATE TEMPORARY TABLE tmp_user_summary AS SELECT ...,并为关联字段加索引 - 避免在临时表里存
TEXT或BLOB字段,它们会拖慢磁盘 IO 和内存拷贝 - 用完记得
DROP TEMPORARY TABLE,尤其在存储过程中,否则可能撑爆 tempdb
真正卡住性能的,往往不是 JOIN 本身,而是派生表输出了不该输出的数据——字段多一行、时间范围宽一天、少一个索引,中间结果集就可能翻几倍。控制它,得从子查询内部开始动手。

















