LEFT JOIN中NULL= NULL不匹配是SQL三值逻辑所致,因返回UNKNOWN而非TRUE;正确处理应使用(a.id = b.user_id) OR (a.id IS NULL AND b.user_id IS NULL)或IS NOT DISTINCT FROM(若数据库支持)。

LEFT JOIN 的 ON 条件里写 a.id = b.user_id,但两边都是 NULL 就不匹配
这不是数据库 bug,是 SQL 三值逻辑的必然结果:NULL = NULL 返回 UNKNOWN,而 JOIN 只保留条件为 TRUE 的行。所以哪怕左表和右表的关联字段都为 NULL,也不会被当作“相等”连上。
常见现象:你查订单和用户,发现 user_id IS NULL 的订单在 LEFT JOIN 后,对应用户字段全为 NULL——这没错;但如果你还指望这些 NULL 能跟另一张配置表里 user_id IS NULL 的默认配置连上,那就失败了。
- 别用
COALESCE(a.id, 0) = COALESCE(b.user_id, 0)去“强行匹配”,它会让索引失效,JOIN 变成全表扫描 - PostgreSQL 或 MySQL 8.0.16+ 可直接用
a.id IS NOT DISTINCT FROM b.user_id,语义清晰且能走索引(部分优化器支持) - SQL Server / Oracle 必须手写:
(a.id = b.user_id) OR (a.id IS NULL AND b.user_id IS NULL)
LEFT JOIN 后加 WHERE b.status = 'active',结果变成 INNER JOIN
WHERE 是在连接完成之后才执行的过滤。一旦右表没匹配上,b.status 就是 NULL,而 NULL = 'active' 是 UNKNOWN,整行被 WHERE 干掉——左表那些本该保留的记录就消失了。
典型误用场景:想查“所有订单 + 对应的活跃用户信息(没有就不填)”,却写出 LEFT JOIN users u ON o.user_id = u.id WHERE u.status = 'active',结果只返回有活跃用户的订单。
- 正确做法:把业务条件挪进 ON 子句:
LEFT JOIN users u ON o.user_id = u.id AND u.status = 'active' - 如果必须用 WHERE,得显式兼容 NULL:
WHERE u.status = 'active' OR u.status IS NULL,但这会改变语义,慎用 - 注意:加了
u.deleted_at IS NULL这类软删条件,同样要放进 ON,否则也触发隐式 INNER JOIN
JOIN 字段本身含 NULL,但又不能改源数据,怎么兼顾性能和语义?
用函数包装字段(比如 ISNULL()、COALESCE())做连接,等于主动放弃索引。执行计划里 type=ALL 或 key=NULL 就是信号。
真正可落地的折中方案,不是在 ON 里算,而是提前把 NULL “标准化”:
- 用 CTE 或派生表预处理右表:
(SELECT id, COALESCE(user_id, -1) AS user_id FROM users) u,再跟左表原始字段连接 - 如果 NULL 代表“全局默认”,可在右表加一列
is_default标记,并确保该列有索引,JOIN 改用布尔条件驱动 - 短期救急可用
STRAIGHT_JOIN(MySQL)或/*+ USE_HASH */(Oracle)强制优化器走特定路径,但只是掩盖问题
不同数据库对 NULL 匹配的支持差异很实际
别写一次 SQL 就假设它能在所有库跑通。IS NOT DISTINCT FROM 在 PostgreSQL 里开箱即用,在 MySQL 8.0.16+ 中仅限部分上下文生效,SQL Server 完全不认。
跨库项目尤其要注意:你写的 COALESCE() 在 SQL Server 里类型推导可能意外扩大字段长度,而在 MySQL 里若参数字符集不一致,还会触发隐式转换导致索引失效。
- 最稳的写法是手写
(a.col = b.col) OR (a.col IS NULL AND b.col IS NULL),所有主流数据库都支持,且现代优化器基本都能识别并走索引 - 如果用的是 HQL 或 Spark SQL,确认目标引擎是否支持
IS NOT DISTINCT FROM,否则运行时报错比结果错更难排查 - 上线前务必在目标环境跑
EXPLAIN或EXPLAIN FORMAT=JSON,看 key 和 type 列有没有异常
COALESCE 看似快,往往埋下性能债。

















