LEFT JOIN中ON和WHERE作用阶段不同:ON在连接时生效,保留左表所有行;WHERE在连接后过滤,剔除不满足条件的整行。

LEFT JOIN里ON和WHERE放条件,结果完全不同
外连接中,ON和WHERE不是换位置就能用的替代品,它们作用阶段不同,直接决定左表行是否被保留。
关键区别在于:ON在生成临时连接表时生效,不满足条件的右表字段填NULL,但左表行一定保留;WHERE是在连接表生成后才过滤,不满足条件的整行(包括左表数据)都会被剔除。
- 写成
LEFT JOIN ... ON a.id = b.user_id AND b.status = 'paid':所有用户都出现,只有已支付订单才带出订单数据,未支付或无订单的用户订单字段为NULL - 写成
LEFT JOIN ... ON a.id = b.user_id WHERE b.status = 'paid':结果等价于INNER JOIN,没匹配到已支付订单的用户直接消失
这是最容易踩的坑——本想查“所有用户+他们的已支付订单”,却因错放WHERE变成了“只查有已支付订单的用户”。
INNER JOIN里ON和WHERE效果一样,但执行逻辑不同
内连接下,把条件写在ON或WHERE里,最终结果通常一致,但数据库执行路径不一样。
ON先执行,在连接过程中就筛掉不匹配的行,中间结果集更小;WHERE后执行,是先完成全量连接再过滤。虽然优化器常能重写等价,但语义上仍应优先把关联逻辑放ON,业务筛选放WHERE。
- 推荐写法:
INNER JOIN orders o ON u.id = o.user_id WHERE o.created_at > '2025-01-01' - 不推荐混用:
INNER JOIN orders o ON u.id = o.user_id AND o.created_at > '2025-01-01'—— 把时间筛选塞进ON,可读性差,且掩盖了它本属于结果过滤意图
ON只能出现在JOIN子句里,WHERE不能替代它的语法角色
ON不是通用过滤关键字,它只存在于JOIN语句中,且必须紧随JOIN之后。单独查询、子查询、UPDATE/DELETE里都不能用ON。
比如这些写法会报错:
-
SELECT * FROM users ON id = 1→ 缺少JOIN,语法错误 -
UPDATE users SET name='x' ON id = 1→ON不能用于UPDATE
而WHERE是通用谓词子句,几乎所有DML语句都支持。别指望用WHERE去“模拟”ON的连接行为——它没有连接能力,也不参与表间匹配逻辑。
性能和可维护性:ON早筛,WHERE晚筛,别让WHERE扛不该扛的活
在涉及大表连接时,ON条件越早筛掉无效行,后续JOIN计算量越小;WHERE延迟过滤,可能让数据库多处理几倍中间数据。
尤其当右表很大、但实际只需匹配少量记录时,把高选择性条件(如状态、日期范围)放进ON,比丢给WHERE更高效。
- 好:
LEFT JOIN logs l ON u.id = l.user_id AND l.event_type = 'login' AND l.created_at >= NOW() - INTERVAL 7 DAY - 差:
LEFT JOIN logs l ON u.id = l.user_id WHERE l.event_type = 'login' AND l.created_at >= NOW() - INTERVAL 7 DAY—— 先连全量日志再过滤,内存和CPU压力明显上升
真正容易被忽略的是:很多团队把所有条件习惯性堆在WHERE末尾,既模糊了连接意图,又悄悄拖慢了查询。看清哪条逻辑属于“怎么连”,哪条属于“连完要什么”,比调参更治本。

















