LEFT JOIN 行数暴增主因是右表关联键重复、ON 条件缺失或恒真、WHERE 误筛右表字段;需先查右表键唯一性、验证有效等值条件、将右表过滤移至 ON 子句。
因为 LEFT JOIN 本身不压缩、不去重,左表一行匹配右表 N 行,就生成 N 行结果——这不是 bug,是 SQL 标准行为。
LEFT JOIN 后行数暴增,大概率是右表关联键重复
比如 orders 表有 1 万行,customers 表里 customer_id = 123 出现了 5 次,那订单表中所有 customer_id = 123 的行(假设 200 行),每行都会被展开成 5 行,光这部分就多出 800 行。
- 先验证:跑
SELECT customer_id, COUNT(*) FROM customers GROUP BY customer_id HAVING COUNT(*) > 1 - 如果返回结果非空,说明右表键不唯一,问题定位完成
- 别只查主键字段名是否叫
id或customer_id,要查它实际值是否真唯一 - 历史数据迁移、ETL 失败、手动补录都可能导致重复,不能默认维表干净
ON 条件缺失或恒真,等价于笛卡尔积
漏写 ON 或写成 ON 1=1,会让数据库把左表每一行和右表每一行都配一遍。1 万 × 5 千 = 5 千万行,执行可能卡死,EXPLAIN 里 rows 会显示天文数字。
- 检查 SQL 是否真写了有效等值条件,例如
o.user_id = u.id,而不是o.status = u.status这类无业务意义的“假连接” - 动态拼 SQL 时,确认
joinCond变量不为空字符串 - 用视图/CTE 封装后,
ON条件没透传进来,也会表现成“没条件” - 多表 JOIN 时,第二层
ON必须延续上一层主键链,比如orders.id = order_items.order_id,不能跳过直接写users.id = order_items.user_id
WHERE 筛右表字段,悄悄把 LEFT JOIN 变成 INNER JOIN
LEFT JOIN 本应保留左表全部行,但一旦在 WHERE 里加了 t2.status = 'paid',NULL 行就被过滤掉了——因为 NULL = 'paid' 是 false,整行消失。
- 右表字段过滤必须写进
ON子句,例如:LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid' - 若需保留左表所有用户,同时只取已支付订单,又想让未支付用户也显示,就得用
WHERE o.status IS NULL OR o.status = 'paid' - 这个逻辑陷阱不会报错,但结果集大小和语义都已偏离预期
真正难排查的不是“哪行多出来了”,而是“为什么我们认为它不该多”——业务上认定的一对一关系,数据库可不管,它只认数据事实。查重复、看执行计划里的 filtered 值、验证 ON 是否生效,这三步比调优索引更优先。