先GROUP BY再JOIN更快,因避免中间结果集爆炸:orders百万行与users十万行一对多关联后或达500万行,而先按user_id聚合仅产出10万行汇总,再JOIN大幅压缩数据量。

先 GROUP BY 再 JOIN 为什么快?中间结果集不爆炸
因为数据库不用把所有明细行先拼出来。比如 orders 表有 100 万行,users 表有 10 万行,一对多关联后中间结果可能达 500 万行甚至更多;而先对 orders 按 user_id 执行 GROUP BY,只产出 10 万行汇总结果,再和 users 关联,总数据量压到可控范围。
哪些写法实际触发了“先 JOIN 后 GROUP BY”陷阱
以下 SQL 看似合理,但优化器常误判执行顺序:
-
SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id——orders全量参与 JOIN,哪怕只统计用户数 -
SELECT u.name, SUM(o.amount) FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid' GROUP BY u.id—— 过滤条件在 JOIN 后,无法阻止中间膨胀 - 嵌套子查询写成
SELECT u.name, (SELECT SUM(amount) FROM orders o WHERE o.user_id = u.id)—— 每行users都触发一次全表扫描,O(N×M) 复杂度
怎么写才能真正让数据库“先聚合再关联”
核心是把聚合逻辑显式隔离在子查询或 CTE 中,并确保关联字段可驱动索引:
- 用子查询包裹聚合:例如
(SELECT user_id, COUNT(*) cnt, SUM(amount) total FROM orders WHERE created_at >= '2026-01-01' GROUP BY user_id),外层再LEFT JOIN - MySQL 8.0+ 推荐用 CTE 提高可读性:
WITH order_agg AS (SELECT user_id, SUM(amount) s FROM orders WHERE status='done' GROUP BY user_id) SELECT u.name, COALESCE(o.s, 0) FROM users u LEFT JOIN order_agg o ON u.id = o.user_id - 必须给子查询里的
GROUP BY字段(如user_id)建索引,否则子查询本身仍是全表扫描 - 如果外层有
WHERE u.level = 'vip',过滤不能只放外层——得下推到子查询里,或改用EXISTS剪枝,否则会统计无效用户再丢弃
容易被忽略的三个硬性前提
光写对语法不够,这三个点漏掉一个,“先聚合”就白做:
-
JOIN字段字符集和排序规则必须一致,比如两个utf8mb4_0900_ai_ci才能走索引;混用utf8_general_ci会导致隐式转换、索引失效 - 维度表(如
users)最好有主键或唯一约束,否则优化器不敢假设关联后不膨胀,可能拒绝下推聚合 - 聚合结果中
user_id列必须为NOT NULL,否则ON u.id = o.user_id会因 NULL 不匹配而丢行;必要时加WHERE o.user_id IS NOT NULL


















