在 MySQL 数据库开发中,LEFT JOIN(左外连接)是一个最常被误用的语法。许多开发者往往习惯性地将所有过滤条件一股脑地往ON后面塞,或者为了排版好看将条件全部扔到WREHRE里面。
这种模糊的逻辑在普通内连接(INNER JOIN)中确实没有区别,但在LEFT JOIN中,多条件写在 ON 还是 WHERE,会导致完全不同的执行结果与查询性能。
1. 核心结论:一句话总结
- 写在
ON里面:代表连接条件。不论右表是否满足此条件,左(驱动)表的数据绝对不会丢。若右表不满足,右表字段直接补NULL。 - 写在
WHERE里面:代表过滤条件。它是对关联后的整个结果集进行大筛查。一旦右表字段因不满足而被补了NULL,它就会在WHERE过滤中被彻底抹去。
2. 真实案例单步拆解
为了还原现场,我们准备两张最简单的基础表:
表 a(商品表)
| f1 (商品ID) | name (商品名) |
|---|---|
| 1 | 苹果 |
| 2 | 香蕉 |
表 b(价格表)
| f1 (商品ID) | f2 (促销价格) |
|---|---|
| 1 | 30 |
| 2 | 50 |
场景一:多条件写在ON之中(促销条件关联)
SELECT*FROMaLEFTJOINbON(a.f1=b.f1ANDb.f2=30);MySQL 底层单步执行逻辑:
由于是LEFT JOIN,表a被指定为驱动表。执行器会拿着表a的数据,一行行去匹配表b,匹配的硬性考核指标是:a.f1 = b.f1并且b.f2 = 30。
- **处理“苹果”行
(1, '苹果')**:拿着f1=1去表b找,找到了满足b.f1=1的行,紧接着评估第二个条件b.f2=30。此时30=30匹配成功。
- 本步结果→\rightarrow→
(1, '苹果', 1, 30)。
- **处理“香蕉”行
(2, '香蕉')**:拿着f1=2去表b找,找到了b.f1=2的行,但它的b.f2是 50,不满足b.f2=30的要求。
- 关键机制:因为是
LEFT JOIN,左表数据不能丢。既然表b没有行能同时满足这两个条件,那就强行输出“香蕉”,右表全部填NULL。 - 本步结果→\rightarrow→
(2, '香蕉', NULL, NULL)。
最终场景一输出结果:
| a.f1 | a.name | b.f1 | b.f2 |
|---|---|---|---|
| 1 | 苹果 | 1 | 30 |
| 2 | 香蕉 | NULL | NULL |
语义总结:“我只想和表 b 中价格是 30 的促销项连。如果它不是 30,我就不跟它连,但我自己(表 a)依然要保留,右边展示为 NULL 即可。”
场景二:条件挪到WHERE之中(连接后过滤)
SELECT*FROMaLEFTJOINbON(a.f1=b.f1)WHEREb.f2=30;MySQL 底层单步执行逻辑:
- 第一阶段(连接):首先只看
ON (a.f1 = b.f1),此时“苹果”和“香蕉”都能正常连上表b。生成一个中间临时结果集:
- 记录一:
(1, '苹果', 1, 30) - 记录二:
(2, '香蕉', 2, 50)
- 第二阶段(大过滤):连接完全结束后,
WHERE b.f2 = 30开始收网。执行器检查上面两条记录,发现记录二的b.f2是 50,不符合WHERE条件,当场予以剔除(消失)。
最终场景二输出结果:
| a.f1 | a.name | b.f1 | b.f2 |
|---|---|---|---|
| 1 | 苹果 | 1 | 30 |
⚠️ 惊人的幕后优化:驱动表被调换了!
在场景二中,既然WHERE条件强制限定了b.f2 = 30,这意味着表b中所有不匹配或者补NULL的行统统都是无效的。
MySQL 优化器敏锐地发现了这一点,会在后台默默将这个LEFT JOIN直接改写为内连接(INNER JOIN)。改写后,由于表b带有b.f2 = 30的强过滤条件,数据集更小,右表b反而会反客为主,变成真正的驱动表。
3. 避坑指南:如何防范?
为了避免線上环境列表无故少数据,或者多出了全是NULL的垃圾数据,我们在写外连接时需要遵守以下原则:
- 明确逻辑目的:
- 如果你要找的是“不满足某些条件的残缺对照”(例如:查出所有没有参加 30 元促销的商品),必须把条件写在
ON里,并配合右表主键IS NULL过滤。 - 如果你仅仅是想对右表进行结果筛选,请直接写
JOIN,不要写LEFT JOIN。
- 利用
EXPLAIN脑补流程:
如果你写了LEFT JOIN却在Extra字段里看到了Using where,且第一行的表(驱动表)变成了右表,说明你的WHERE条件已经打破了LEFT JOIN的语义,被优化器降级改写了。
在处理多表关联的业务(如复杂报表、用户主页信息流)时,多花半分钟确认过滤条件在ON还是WHERE,能为你省下大量的线上 Debug 时间。