on只决定连接匹配,不负责最终过滤;left join中业务条件应放where而非on,否则会误删左表行;where过滤右表字段会使left join退化为inner join;优化应关注explain的filtered值变化。

ON 条件里不能随便塞 WHERE 逻辑
JOIN 的 ON 子句只决定「哪些行能被连上」,不是过滤最终结果的工具。比如 LEFT JOIN 后在 ON 里写 t2.status = 'active',会把 t2 中不满足该条件的行当成 NULL 补上,而不是直接剔除——这和写在 WHERE 里效果完全不同。
常见错误现象:LEFT JOIN 结果里本该有左表全部记录,但实际只剩部分;或者右表字段全为 NULL 却没意识到是 ON 条件太严。
- 想保留左表所有行?
ON里只放关联条件(如t1.id = t2.t1_id),状态、时间等业务过滤一律挪到WHERE - 想只连上「活跃用户」的订单?用
INNER JOIN+ON ... AND t2.status = 'active'可以,但等价写法是INNER JOIN (...) ON ... WHERE t2.status = 'active'—— 前者更清晰表达意图 -
ON中混用非关联字段,可能让优化器放弃使用索引,尤其当字段来自右表且无索引时
WHERE 过滤 NULL 会让 LEFT JOIN 变成 INNER JOIN
这是最常踩的坑:在 LEFT JOIN 后加 WHERE t2.id IS NOT NULL 或 WHERE t2.name != 'test',表面看只是筛数据,实际等效于强制要求右表必须有匹配行——语义上已退化为 INNER JOIN。
使用场景:你真需要「左表有、右表无」的记录(比如查没下过单的用户),就不能在 WHERE 里碰右表任何字段,哪怕只是判 IS NULL 也要确认是否真想排除掉右表有值的行。
-
WHERE t2.id IS NULL是合法且常用的需求(找未匹配项) -
WHERE t2.id IS NOT NULL≡ 把LEFT JOIN当INNER JOIN用,不如直接改写,避免误导后续维护者 - 如果必须混合过滤(比如「左表用户 + 右表活跃订单 + 订单金额 > 100」),优先把右表业务条件放在
ON(对INNER JOIN)或子查询中,别依赖WHERE拦截
多表 JOIN 时 ON 的顺序影响可读性,不影响执行计划
SQL 标准不规定 ON 必须紧贴对应 JOIN,但人读的时候容易混乱。比如 FROM t1 JOIN t2 ON ... JOIN t3 ON ...,第二个 ON 是修饰 t2 JOIN t3 还是 t1 JOIN t2?答案是前者,但缩进或换行不对就极易误读。
性能上,现代优化器基本能重排 JOIN 顺序,ON 写在哪一行不改变实际执行路径;但可读性和后期修改成本差别很大。
- 每个
JOIN后立刻跟它的ON,不要堆到最后统一写 - 涉及复合条件(如
t2.a = t1.a AND t2.b = t3.b),说明设计已有缺陷——跨三个表的关联条件应拆到子查询或视图里,否则难以维护 - 用括号明确嵌套关系:比如
(t1 JOIN t2 ON ...) JOIN t3 ON ...,尤其在混合LEFT/INNER时能避免歧义
EXPLAIN 看不出 ON 和 WHERE 的语义区别,得看 rows 和 filtered
EXPLAIN 输出里的 type、key、rows 能反映实际扫描量,但不会告诉你哪条过滤是在 JOIN 阶段应用、哪条是最后筛。真正关键的是 filtered 列——它表示该表在应用完所有条件后,剩余行数占扫描行数的百分比。
举个例子:如果 t2 的 filtered 是 10%,而你在 ON 里写了 t2.status = 'active',说明这个条件确实参与了驱动连接,但如果 filtered 是 100%,大概率是条件被移到了最后一步过滤,没起到提前剪枝作用。
- 对比两次
EXPLAIN:一次把条件放ON,一次放WHERE,重点看右表的rows和filtered是否变化 - 如果
rows不变但filtered从 100% 降到 5%,说明条件成功下推到了 JOIN 阶段,值得保留 - MySQL 8.0+ 的
FORMAT=JSON更直观,关注attached_condition字段出现的位置











