聚合异常、执行慢的主因是JOIN失控引发笛卡尔积:漏ON条件、ON写错位置、关联字段重复/NULL/类型不匹配。用COUNT(*) OVER(PARTITION BY...)快速定位膨胀,逐查每个JOIN的ON子句有效性及数据质量。

聚合查询结果翻倍、SUM()虚高、执行慢到超时——不是GROUP BY写错了,而是前面的JOIN已经失控,产生了笛卡尔积式膨胀。
看执行计划里有没有Nested Loop (Join Filter: true)
PostgreSQL 中只要看到这个节点,基本等于确认:漏写了ON条件。数据库被迫对左表每一行都扫描右表全量,等效于隐式CROSS JOIN。MySQL 对应的是type: ALL配合Extra: Using join buffer,尤其多表时某张表出现这组合,别犹豫,立刻翻 SQL 查它前面那个JOIN有没有紧跟着有效的ON子句。
- 逐个检查每个
JOIN后是否立刻跟了ON,且至少含一个左表列和一个右表列(如ON o.id = oi.order_id) - 警惕
ON 1=1、ON u.id = u.id这类恒真或自等式,语法合法但语义失效 - 老式逗号语法
FROM a, b必须靠WHERE补关联,缺一个字段等于没写
用COUNT(*) OVER (PARTITION BY ...)快速验证膨胀程度
不跑全量SELECT *,加个窗口函数就能看出单条主表记录拉出了多少从表行。比如查用户订单金额总和异常,先写:
SELECT u.id, u.name, COUNT(*) OVER (PARTITION BY u.id) AS order_cnt FROM users u LEFT JOIN orders o ON u.id = o.user_id;
如果某个u.id对应的order_cnt动辄几百上千,而你预期最多几十,那不是数据本身多,是连接逻辑崩了。
- 关注最大值和分布:
ORDER BY order_cnt DESC LIMIT 5往往一眼揪出问题源头 - 如果结果里出现
order_cnt = 0却又大量非零值,说明右表存在重复user_id或NULL - 这个技巧在线上环境也安全,不改逻辑、不锁表,只加计算
LEFT JOIN中WHERE和ON放错位置会放大中间结果集
这不是语法错误,但效果类似笛卡尔积:本该被过滤掉的右表行,因为错误塞进WHERE,导致LEFT JOIN先全量配对再过滤,内存和 IO 都白花了。
- 错误写法:
LEFT JOIN customers c ON o.customer_id = c.id WHERE c.status = 'active'→ 先生成所有c.status IS NULL的组合,再干掉 - 正确写法:
LEFT JOIN customers c ON o.customer_id = c.id AND c.status = 'active'→ 只拉符合条件的右表行,从源头控量 - 如果业务需要保留无客户匹配的订单,又只取活跃客户信息,
WHERE绝对不能碰右表字段
关联字段有重复值或NULL也会让聚合失真
即使ON写对了,orders.order_id在明细表里重复两次、或order_items.order_id有NULL,都会让JOIN结果成倍膨胀。这不是语法问题,是数据质量问题。
- 先查重复:
SELECT order_id, COUNT(*) FROM order_items GROUP BY order_id HAVING COUNT(*) > 1 - 再查
NULL:SELECT COUNT(*) FROM order_items WHERE order_id IS NULL - 类型不一致也会触发隐式转换,比如
t1.id是BIGINT,t2.t1_id是VARCHAR,索引失效,优化器可能退化为哈希连接,中间结果照样爆炸
真正难的不是写出GROUP BY,而是判断哪张表该提前聚合、按什么字段去重、状态过滤要不要下推到子查询里——这些都得贴着业务逻辑抠,漏一个AND deleted = 0,结果还是错的。

















