LEFT JOIN右表条件必须写在ON中,否则WHERE过滤会剔除左表无匹配行;INNER JOIN下ON与WHERE语义不同,前者影响连接过程,后者作用于结果集,性能与执行路径可能差异显著。

LEFT JOIN 时右表条件必须写在 ON 中,否则左表数据会被意外过滤;INNER JOIN 虽然结果常一致,但语义和性能差异真实存在,不能靠“反正一样”混用。
LEFT JOIN 右表条件写在 WHERE 就会丢数据
这是最常见也最隐蔽的 Bug 来源。比如查所有用户及其「已支付」订单:LEFT JOIN orders o ON u.id = o.user_id 是基础连接,但若把 o.status = 'paid' 放到 WHERE,那些没订单或订单未支付的用户就全没了——WHERE 在连接后执行,会把 o.status 为 NULL 的行直接踢掉。
- 正确写法:
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid' - 错误现象:结果行数骤减,
EXPLAIN显示Extra: Using where,说明过滤发生在连接后 - 唯一例外是
WHERE o.id IS NULL这类显式筛选 NULL 的场景,它本意就是找“没匹配上的左表行”
INNER JOIN 下 ON 和 WHERE 不是真等价
表面看三条语句都返回相同结果,但执行路径可能完全不同:
-
INNER JOIN b ON a.id = b.a_id AND b.deleted = 0:数据库可能下推deleted = 0到b表扫描阶段,提前跳过 99% 的行 -
INNER JOIN b ON a.id = b.a_id WHERE b.deleted = 0:先完成全量连接,再对结果二次过滤,中间结果更大 - 一旦引入子查询、CTE 或三表以上连接,优化器未必能重写等价,行为可能分裂
多表 JOIN 时 ON 条件顺序影响可读性与性能
写成 FROM a JOIN b ON a.id = b.a_id JOIN c ON b.id = c.b_id 是清晰的链式依赖;但如果把所有 ON 堆到最后,或缩进错乱,人眼极易误读哪条条件属于哪个 JOIN。
- 高选择性条件(如
c.created_at > '2024-01-01')应优先放在对应JOIN的ON中,让优化器尽早剪枝 -
ON里禁止引用前面未JOIN的表字段(MySQL 直接报错,PostgreSQL 允许但语义模糊) - 复合条件如
t2.a = t1.a AND t2.b = t3.b暗示表设计缺陷,应拆到子查询中处理
真正难的不是语法,而是每次写 ON 或 WHERE 时,得问自己一句:这个条件是在定义“怎么连”,还是在定义“要哪些结果”。前者归 ON,后者归 WHERE——漏掉这层判断,EXPLAIN 看不出问题,线上数据却已经不对了。

















