LEFT JOIN 中 ON 条件保留左表所有行,仅对右表匹配行过滤;WHERE 则在连接后筛选,会剔除右表为 NULL 的行,使 LEFT JOIN 退化为 INNER JOIN;INNER JOIN 中 ON 与 WHERE 位置通常等效,但需注意 NULL 处理;多表连接时 ON 只作用于紧邻右表,且条件下推影响性能。

LEFT JOIN 中 ON 条件会保留左表所有行
当你写 LEFT JOIN ... ON a.id = b.id AND b.status = 'active',数据库先尝试用全部左表记录去匹配右表中 status = 'active' 的行;没匹配上的,右表字段全为 NULL,但左表那条记录依然在结果里。这和你直觉里“只查活跃用户”可能不一致——它其实返回的是“所有用户 + 仅活跃用户的附加信息”。常见错误是把业务过滤逻辑(比如 user_type = 'vip')塞进 ON,结果发现非 VIP 用户也出现在结果中,只是右表字段为空。
WHERE 是对连接后整张临时表的最终筛选
WHERE 执行阶段晚于 ON,此时临时表已生成,NULL 值也已确定。所以 WHERE b.status = 'active' 会直接剔除所有 b.status 为 NULL 或不等于 'active' 的行——包括原本左表有数据、但右表没匹配上的那些。这就让 LEFT JOIN 实际退化成了 INNER JOIN。典型坑点:写 WHERE b.name IS NOT NULL 后,发现左表“丢失”了没关联上的记录,其实不是丢,是被 WHERE 删掉了。
INNER JOIN 里 ON 和 WHERE 换位置通常没区别
因为内连接本就不保证任何一方的完整性,只返回匹配成功的交集。所以 INNER JOIN t1 ON t1.id = t2.id WHERE t2.deleted = 0 和把 t2.deleted = 0 移到 ON 里,结果一致。但注意:如果 t2.deleted 字段允许 NULL,而你用了 WHERE t2.deleted != 1,就可能漏掉 NULL 行;换成 ON 同样会漏——这不是位置问题,是 != 对 NULL 的语义问题。真正影响性能的是条件能否下推:数据库更倾向把能过滤掉大量右表行的条件放在 ON,减少中间表大小。
多表 LEFT JOIN 时,ON 条件只作用于紧邻的右表
像 SELECT * FROM a LEFT JOIN b ON a.id = b.aid LEFT JOIN c ON b.id = c.bid AND c.flag = 1,第二个 ON 里的 c.flag = 1 只控制 c 表参与连接的行,不影响 a 或 b。但如果写成 WHERE c.flag = 1,就会把 b 有数据但 c 不满足 flag = 1 的整行干掉——哪怕你只想保留 a 和 b 的组合。这种嵌套层级越深,ON 放错位置越容易导致意外空值或数据截断。
最易被忽略的其实是执行顺序带来的隐性依赖:ON 阶段右表还没被 WHERE 筛过,所以 ON 里引用右表字段做等值判断是安全的;但若在 ON 中写 b.created_at > '2025-01-01',而该字段在右表中存在大量 NULL,就可能因索引失效或类型隐式转换拖慢整个连接过程——这比结果错更难排查。










