left join后行数膨胀是必然现象,源于右表连接键存在一对多关系(如一个customer_id对应多条记录),导致左表每行被重复展开;需通过count(*)对比验证、group by检查右表键重复性,并将过滤条件置于on而非where中。

LEFT JOIN后行数异常膨胀不是“可能出错”,而是只要关联逻辑没对齐,它就一定发生——而且往往在你看到结果前,数据库已经把内存和IO耗光了。
确认膨胀是否真实存在
别急着改SQL,先验证问题是否存在:
- 单独查左表行数:
SELECT COUNT(*) FROM orders,记下这个数字 - 再执行完整LEFT JOIN查询,加
COUNT(*)看总行数 - 如果后者明显大于前者,说明膨胀已发生;如果接近或相等,问题可能出在后续GROUP BY或WHERE上
检查右表连接键是否唯一
这是最常被忽略的根因。LEFT JOIN不保证右表只匹配一行,它只保证左表全量保留——前提是右表在连接键上真的一对一。
- 查右表重复键:
SELECT customer_id, COUNT(*) FROM customers GROUP BY customer_id HAVING COUNT(*) > 1 - 如果返回结果,说明一个
customer_id对应多条记录,JOIN时就会把左表同一行撑开多次 - 修复方式不是强行去重,而是明确业务意图:要最新地址?用
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY update_time DESC)取首行;要统计数量?那就该用聚合,而不是裸JOIN
LEFT JOIN过滤条件必须写在ON里,不能放WHERE
这是线上最高频的逻辑陷阱。放在WHERE里,等于先全量配对再砍掉NULL和不匹配行,中间结果早已爆炸。
- 错误写法:
LEFT JOIN customers c ON o.customer_id = c.id WHERE c.status = 'active'→ 实际等效INNER JOIN,且白跑一遍NULL匹配 - 正确写法:
LEFT JOIN customers c ON o.customer_id = c.id AND c.status = 'active'→ 从源头只拉活跃客户,左表所有订单都保留 - 特别注意:MySQL 5.7+、PostgreSQL支持良好,但某些旧版SQLite或ODBC驱动可能不认
AND后的条件,上线前务必实测
多表JOIN时避免隐式笛卡尔积
两个LEFT JOIN串在一起,如果右表各自一对多,结果就是乘法级膨胀——比如A表1行 × B表3行 × C表2行 = 6行,而不是你预期的1+3+2=6行。
- 典型错误:
SELECT a.pr_code, COUNT(b.id), COUNT(c.id) FROM a LEFT JOIN b ON a.pr_code = b.pr_code LEFT JOIN c ON a.pr_code = c.pr_code GROUP BY a.pr_code - 此时
COUNT(c.id)会变成3(B的行数),不是C的真实计数 - 解法是各表独立聚合再合并:
SELECT pr_code, SUM(b_cnt), SUM(c_cnt) FROM (SELECT pr_code, COUNT(*) AS b_cnt, 0 AS c_cnt FROM b GROUP BY pr_code UNION ALL SELECT pr_code, 0 AS b_cnt, COUNT(*) AS c_cnt FROM c GROUP BY pr_code) t GROUP BY pr_code
真正难的不是写出能跑的SQL,而是判断哪张表该聚合、哪个键该去重、哪条过滤该提前——这些决定都在JOIN之前就该想清楚,而不是靠DISTINCT或窗口函数在最后硬捞。











