LEFT JOIN中WHERE过滤右表字段会变相转为INNER JOIN,正确做法是将条件移至ON子句;ON只负责关联逻辑,WHERE专用于业务过滤,混淆二者易致性能下降或数据丢失。

LEFT JOIN里WHERE筛右表字段=变相INNER JOIN
这是最常踩的坑:明明写了LEFT JOIN,结果左表数据少了一半。根本原因是WHERE对右表字段做了非空判断,比如WHERE u.status = 'active'——数据库先完成连接(没匹配上的u.status是NULL),再执行WHERE,而NULL = 'active'恒为false,整行被剔除。
想保留所有左表行,又只连“活跃用户”,必须把条件挪进ON:LEFT JOIN users u ON u.id = o.user_id AND u.status = 'active'。这时不满足u.status = 'active'的用户,只是让右表字段全为NULL,左表订单行仍在。
-
WHERE t2.id IS NULL是安全的(找未匹配项),但WHERE t2.id IS NOT NULL等价于INNER JOIN,不如直接改写 - MySQL 5.7+会警告
WHERE中右表字段判非空,但不报错,容易误以为逻辑正确 - 某些老版本MySQL或Presto对
WHERE中右表条件的下推行为不一致,换库就出问题
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标准保证的行为。
把业务筛选(如b.deleted = 0)塞进ON,后续想改成LEFT JOIN时就得同步改逻辑,极易遗漏;而且ON里混入非关联字段(比如右表无索引的状态字段),可能让优化器放弃使用索引,拖慢查询。
-
ON只该放关联逻辑:外键匹配、分片键对齐、主从表关系表达式 -
WHERE专管业务过滤:时间范围、状态、权限字段、用户输入参数 - 子查询或CTE里提前过滤右表,比依赖
WHERE拦截更可控
多表JOIN时ON顺序影响可读性,但别指望它控制执行计划
SQL标准不规定ON必须紧贴对应JOIN,但人读的时候容易混乱。比如FROM t1 JOIN t2 ON ... JOIN t3 ON ...,第二个ON实际修饰的是t2 JOIN t3,不是t1 JOIN t2。缩进不对或换行随意,维护时一眼看错关联关系。
更麻烦的是跨数据库兼容性:PostgreSQL对ON引用未引入表别名报错,而MySQL可能容忍但结果不可靠;Presto在复杂嵌套JOIN中对ON位置更敏感。
- 每个
JOIN后立刻写对应的ON,别堆到末尾 - 避免在
ON里引用后面才JOIN的表别名(哪怕当前数据库允许) - 用括号明确关联优先级,比如
(t1 JOIN t2 ON ...) JOIN t3 ON ...
EXPLAIN里filtered值暴露ON/WHERE的真实影响
ON条件作用于连接阶段,直接影响中间结果集大小;WHERE作用于连接后,只减少最终输出行数。看EXPLAIN的filtered列能直观验证:如果WHERE里写了右表字段,filtered值往往远低于ON里同样条件——说明数据库被迫先生成大量无效中间行,再过滤掉,白白浪费IO和内存。
尤其当右表很大且无索引时,ON t2.status = 'active'能让连接只扫符合条件的行;而WHERE t2.status = 'active'可能触发全表扫描+临时表排序。
- 用
EXPLAIN FORMAT=JSON查rows_examined_per_scan和filtered,比肉眼猜更准 -
ON中条件若涉及函数(如DATE(t2.created_at)),索引失效风险比WHERE更高 - 真正复杂的过滤逻辑(比如多字段组合、模糊匹配),优先考虑右表预过滤子查询,而非硬塞
ON或WHERE

















