JOIN未写ON条件会生成笛卡尔积,导致结果集爆炸;ON条件恒真或错误等价于无约束;多表JOIN需延续主键链;LEFT JOIN中右表过滤须在ON而非WHERE,否则逻辑失效。

JOIN没写ON条件直接返回笛卡尔积
不加ON的JOIN(尤其是INNER JOIN或LEFT JOIN)在语法上可能不报错,但逻辑上等价于CROSS JOIN,会把左表每行和右表每行强行配对。1万行 × 1万行 = 1亿行,结果集爆炸是常态,不是偶然。
常见诱因包括:
- 复制粘贴时漏掉
ON子句,只留了FROM t1 JOIN t2 - 动态拼SQL时变量为空,导致
ON ${joinCond}展开成空字符串 - 用视图或CTE封装后,误以为关联已内置,实际未透传条件
验证方法:临时改写为SELECT COUNT(*) FROM t1 JOIN t2,若数量接近COUNT(*) FROM t1 × COUNT(*) FROM t2,基本可锁定问题。
ON条件写错成恒真表达式
ON 1=1、ON t1.id IS NOT NULL、ON t1.status = t2.status(但两表该字段无业务关联性)这类条件不会报错,却让数据库失去筛选能力,效果和没写ON几乎一样。
这类错误难排查,因为:
- 执行计划里
type可能显示ALL或index,但rows预估异常高 - 开发环境数据量小,看不出问题;上线后数据增长,查询直接卡死
- DBA看到慢查询日志,第一反应是索引缺失,容易忽略条件本身无效
建议每次写完JOIN,手动检查ON是否含至少一个明确的等值映射(如t1.user_id = t2.owner_id),避免纯逻辑判断。
多表JOIN时中间表未正确约束
三张表以上关联时,若第二层JOIN的ON只关联前一张,没延续主键链,就可能松动连接关系。例如:
SELECT * FROM users u JOIN orders o ON u.id = o.user_id JOIN order_items oi ON o.id = oi.order_id -- ✅ 正确:延续o.id -- 而不是 ON u.id = oi.user_id -- ❌ 错误:跳过orders表,users直接连order_items
后者会导致每个用户匹配其所有订单项(不管是否属于自己的订单),尤其当order_items有历史归档数据时,重复放大极其明显。
更隐蔽的是时间范围遗漏:ON o.id = oi.order_id AND oi.created_at >= '2025-01-01'漏掉时间条件,会让旧订单项也参与关联,数据量陡增。
LEFT JOIN右表无匹配但WHERE又筛右表字段
表面看是“结果过大”,实则是逻辑矛盾被掩盖:LEFT JOIN本应保留左表全部,但若WHERE里写了t2.status = 'paid',数据库会先完成JOIN(此时右表为NULL的行已存在),再执行WHERE——而NULL = 'paid'恒为FALSE,这些行全被剔除,最终只剩右表有匹配且满足状态的行,等效于INNER JOIN。
真正危险的是:这个“等效”不报错、不告警,只默默少返回数据;但若右表恰好有大量空匹配(比如新注册用户还没下单),你反而会误判为“结果集变小”,完全偏离真实问题方向。
记住口诀:ON管“怎么连”,WHERE管“连完后筛谁”;右表的业务过滤必须进ON(LEFT JOIN场景下),否则就不是你想要的“左表全量”。

















