先聚合再关联仅在特定场景有效:维度表极小、事实表极大、关联字段严重倾斜、聚合逻辑复杂或无需明细字段;否则易出错且性能更差。

先聚合再关联不是万能提速方案,只在特定场景下真正有效——多数时候反而是先JOIN再GROUP BY更稳妥。盲目套用容易查出错数据、性能不升反降。
哪些场景真该先聚合再JOIN
只有满足以下至少一条,才值得拆成子查询或CTE预聚合:
- 维度表极小(比如
status_codes只有10行),事实表极大(orders超千万行) -
user_id等关联字段存在严重倾斜(某ID占全部订单70%以上) - 聚合逻辑无法在主查询表达,例如带
FILTER(PostgreSQL)、嵌套窗口函数、多周期统计(7天/30天并行) - 查询只要聚合结果,不依赖明细字段(如只要每个城市的
total_gmv,不要order_id)
子查询预聚合必须写对的三个细节
语法写错一丁点,结果就错、性能崩、甚至查不出数据:
-
子查询必须有别名,否则ON里无法引用字段,MySQL报错Every derived table must have its own alias -
GROUP BY字段必须和JOIN条件字段完全一致:类型相同(BIGINT不能对VARCHAR)、名称一致(user_id不能写成uid)、无隐式转换(避免ON u.id = CAST(agg.user_id AS CHAR)) -
LEFT JOIN后,聚合字段要用COALESCE(agg.total_spent, 0),否则NULL会把整行过滤掉,等效于INNER JOIN
EXPLAIN验证是否真走“先聚后关”路径
光写对语法不够,得看执行计划有没有真正受益:
- 聚合子查询的
type应为ref或range(说明走了索引),而非ALL - 外层
JOIN的rows估算值,应接近子查询聚合后行数(比如1万),而不是明细表原始行数(比如500万) - 如果
Extra列出现Using temporary或Using filesort,说明中间结果仍过大,得检查是否漏了WHERE下推或索引缺失
预聚合真正的复杂点不在SQL写法
而在于索引和统计信息是否到位——再干净的子查询,只要user_id上没索引,或统计信息过期,数据库照样全表扫描。预聚合省下的数据量,全被这一步吃掉。别只盯着GROUP BY怎么写,先跑ANALYZE TABLE orders,再确认EXPLAIN里key列是否命中你建的复合索引。










