先聚合再连接是什么
处理一对多关系时,常规思路是先在小结果集上做过滤与聚合,再与主表连接取名称等字段:把 SUM、COUNT、MAX 放进子查询,副表被压缩成每组一行,连接与后续排序的输入都变小了。相比先连接全部明细再聚合,这种写法能明显减少中间结果。前提是聚合口径能在子查询内算完;如果统计维度依赖主表列,就需要改写或换用别的写法。改写后务必用 EXPLAIN 复核,旧版本可能先把派生表整体物化。
基本信息
| 核心思路 | 过滤与聚合放在子查询,连接发生在小结果集上 |
|---|---|
| 收益 | 减少连接输入行数与聚合中间结果 |
| 版本注意 | MySQL 8.0.22 起支持派生表条件下推,旧版本可能整体物化 |
| PostgreSQL | FROM 子查询一般被内联展开,改写后计划通常直接体现 |
| 口径风险 | 聚合层级不提前定,外层再聚合会把金额重复累计 |
典型使用场景
- 用户列表带订单总额与最近下单时间
- 商品列表带销量、库存与最新售价
- 报表按维度汇总后关联维度名称与属性
- 大表关联小维度:先把大表聚合变小,再连接
- 替代逐行执行的标量子查询,避免外层行数放大时性能骤降
先聚合再连接
先聚合再连接
SELECT u.id, u.name, s.total, s.last_order
FROM users u
JOIN (
SELECT user_id,
SUM(amount) AS total, # 聚合在子查询里完成
MAX(created_at) AS last_order
FROM orders
WHERE status = 1 # 先过滤,缩小聚合输入
GROUP BY user_id
) s ON s.user_id = u.id
ORDER BY s.total DESC;几种写法的差别
| 写法 | 连接输入 | 聚合中间结果 |
|---|---|---|
| 先聚合再连接 | 副表已压缩为每组一行 | 小,只处理分组结果 |
| 先连接再聚合 | 明细展开后的放大行数 | 大,聚合要处理放大后的行 |
| 标量子查询逐行 | 每行执行一次子查询 | 取决于索引与执行次数 |
| CTE 物化一次复用 | 一次计算多次使用 | 需注意物化开销与内存 |
MySQL 8.0.22 起支持派生表条件下推,能把外层过滤条件推进子查询内部;旧版本没有该能力,升级数据库后重跑 EXPLAIN 常能看到计划明显变化,这也是版本迁移后需要复核计划的原因之一。
聚合层级必须提前定:先连接明细再在外层 SUM,会把一对多放大后的行重复累计,金额类指标成倍偏高,这类口径错误往往要等对账时才发现。