left join中on过滤右表,where会过滤含null的左表行导致逻辑变为inner join;正确做法是将右表业务条件移入on,关联条件留on、筛选条件放where。

LEFT JOIN 中 ON 过滤右表,WHERE 却会干掉左表行
LEFT JOIN 的语义是“左表必须全出”,但这个保证只在 ON 阶段有效。一旦进到 WHERE 阶段,数据库已经生成了带 NULL 的临时表,WHERE b.status = 'active' 会把所有 b.status 为 NULL(即没匹配上的左表行)或不等于 'active' 的整行删掉——结果左表“凭空消失”,实际变成 INNER JOIN。
常见错误现象:
-
LEFT JOIN orders o ON u.id = o.user_id WHERE o.paid_at IS NOT NULL→ 所有没订单的用户全没了 -
LEFT JOIN servers s ON r.id = s.rack_id WHERE s.is_virtual = false→ 没绑物理服务器的机架直接不返回
正确做法:想保留左表、只让右表“有条件地参与”,就把过滤写进 ON,比如 ON u.id = o.user_id AND o.status = 'paid'。
INNER JOIN 里 ON 和 WHERE 看似等效,但语义和稳定性不同
对 INNER JOIN 来说,ON a.id = b.id AND b.deleted = 0 和 ON a.id = b.id WHERE b.deleted = 0 通常返回相同结果,但这依赖优化器重写能力,不是 SQL 标准保证的行为。
容易踩的坑:
-
WHERE b.deleted != 1会漏掉b.deleted IS NULL的行,ON里写也一样漏——这不是位置问题,是!=对NULL永远返回UNKNOWN - 换成
WHERE b.deleted = 0 OR b.deleted IS NULL才安全,但逻辑已变,别指望ON自动兜底 - 换到 Presto 或老版 MySQL,
ON里放非关联条件可能被拒绝或执行计划突变
建议:把纯关联逻辑(如外键、分片键)放 ON,业务筛选(状态、时间、权限)统一放 WHERE,保持可读性和跨库兼容性。
多表 LEFT JOIN 时,ON 只管紧邻右表,不能跨层依赖
写 A LEFT JOIN B ON ... LEFT JOIN C ON b.id = c.bid AND c.flag = 1,第二个 ON 中的 c.flag = 1 只影响 C 表哪些行能连上来,它完全不感知 B 是否已被前面条件筛过——因为 ON 是逐对执行的,没有“全局中间态”。
典型误用:
- 以为
WHERE b.name IS NOT NULL能确保C连接时b已存在,其实WHERE在所有JOIN完才执行,C连接时B还是全量的 - 把本该在
WHERE筛左表的条件(如a.category = 'vip')错塞进第二个ON,导致A行数异常膨胀或空匹配
记住:每个 ON 只能引用它左边所有表(A、B)和右边那个表(C),不能引用更右的表,也不能假设左边表已被过滤。
ON 中写右表非等值条件可能隐式拖慢查询
ON 阶段右表还没被 WHERE 处理,所以 ON b.created_at > '2025-01-01' 这类范围条件,如果 b.created_at 存在大量 NULL,可能导致索引失效、类型隐式转换,甚至让优化器放弃使用索引。
对比来看:
-
ON b.id = a.bid AND b.status = 'active'→ 等值 + 高选择率,易走索引,下推高效 -
ON b.id = a.bid AND b.created_at > '2025-01-01'→ 范围扫描,若created_at列基数低或 NULL 多,性能抖动明显
真正难察觉的是:这种慢不报错、不丢数据,只在数据量上去后突然变卡——你得看执行计划里的 rows 和 filtered 才能定位。










