group by 本身不产生笛卡尔积,膨胀发生在 join 阶段;sql 执行顺序为 from → join → where → group by → select;join 后数据已膨胀,group by 仅在错误结果上汇总,无法修复。

GROUP BY 本身不产生笛卡尔积,膨胀发生在 JOIN 阶段
SQL 执行顺序是 FROM → JOIN → WHERE → GROUP BY → SELECT。所谓“JOIN 后 GROUP BY 出问题”,其实是 JOIN 阶段已经把数据撑爆了,GROUP BY 只是在错误的中间结果上做汇总——它不会修复、也无法感知膨胀是否发生。
典型信号:EXPLAIN 中 rows 列数值远超主表实际行数(比如 orders 表 1 万行,JOIN 后预估扫描 80 万行),或出现 Nested Loop (Join Filter: true)(PostgreSQL)/type: ALL + Extra: Using join buffer(MySQL)。
LEFT JOIN 中 WHERE 和 ON 放错位置,等于主动制造膨胀
右表过滤条件写在 WHERE 而非 ON,会导致数据库先全量配对、再剔除,中间过程已膨胀。
- 错误写法:
LEFT JOIN customers c ON o.customer_id = c.id WHERE c.status = 'active'→ 所有客户(含status IS NULL)先 JOIN 进来,再筛,订单行被重复拉取 - 正确写法:
LEFT JOIN customers c ON o.customer_id = c.id AND c.status = 'active'→ 右表只关联活跃客户,从源头控基数 - 特别注意:若业务要求保留无客户匹配的订单,
WHERE绝对不能碰右表字段,否则LEFT JOIN退化为INNER JOIN
一对多关系未预聚合,直接 JOIN 就是行数乘法器
当主表(如 orders)和明细表(如 order_items)是 1:N 关系,又继续 JOIN 第三张表(如 customers),没加约束时,就是 1 × N × M 的组合爆炸。
实操建议:
- 别写:
orders o JOIN order_items i ON o.id = i.order_id JOIN customers c ON o.customer_id = c.id - 改用子查询预聚合:
orders o JOIN (SELECT order_id, SUM(amount) AS total FROM order_items GROUP BY order_id) i ON o.id = i.order_id - 若还需明细字段(如最新下单时间),用
ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY created_at DESC)先筛出单条,再 JOIN
COUNT/SUM 翻倍不是算错了,是按物理行老老实实加的
数据库没 bug,它只是忠实地执行了你写的逻辑:1 行订单 × 3 行订单项 × 2 行客户 = 6 行中间结果,SUM(amount) 就加了 6 次,COUNT(*) 就是 6。
常见误判点:
-
COUNT(DISTINCT order_id)可修正计数,但SUM(DISTINCT amount)是去重金额值,不是去重行,完全无效 -
DISTINCT放在SELECT里是事后收缩,代价高且治标不治本;真正要做的,是让关联表在 JOIN 前就“压缩”成每键一行 - 子查询必须严格
GROUP BY关联字段(如order_id),且过滤条件(如status = 'paid')必须写在子查询内,不能挪到外层WHERE
最易被忽略的一点:膨胀后的中间结果可能仍能跑出“看起来正常”的行数(比如 GROUP BY order_id 后还是 1 万行),但每个分组里的 SUM、AVG 已经虚高——你得看值,而不是看行数。










