最可靠方法是LEFT JOIN + WHERE右表主键IS NULL,例如SELECT u.* FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.id IS NULL;禁用NOT IN以防NULL导致空结果,须确保关联字段有索引且类型一致。

用 LEFT JOIN + IS NULL 找出“孤儿记录”
想查 A 表中那些在 B 表里完全找不到匹配项的记录,最可靠、最通用的做法就是 LEFT JOIN 配合 IS NULL。别用 NOT IN,它在 B 表字段含 NULL 时会返回空结果——这是线上事故高发点。
-
LEFT JOIN必须写在FROM后,关联条件放ON子句,不是WHERE -
IS NULL一定要检查JOIN右表的**主键或非空字段**(比如b.id),不能检查b.name这类可能本来就是NULL的字段 - 示例:查所有没下过单的用户:
SELECT u.* FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.id IS NULL
NOT EXISTS 比 NOT IN 更安全
NOT IN 在子查询结果含 NULL 时整个条件恒为 FALSE,结果集为空;NOT EXISTS 不受 NULL 影响,语义也更贴近“不存在匹配行”。
-
NOT EXISTS子查询必须相关(correlated):里面要引用外层表字段,比如WHERE o.user_id = u.id - 子查询里
SELECT *和SELECT 1效果一样,但写SELECT 1更明确意图 - 示例:
SELECT * FROM users u WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)
EXISTS 和 NOT EXISTS 的性能差异
数据库优化器通常对 EXISTS 和 NOT EXISTS 都能走索引,但实际表现取决于字段选择性、索引覆盖和数据分布。别默认认为 NOT EXISTS 一定慢。
- 确保
JOIN或子查询里的关联字段(如user_id)有索引,否则全表扫描不可避免 - 如果右表数据量极小(比如几十行),
LEFT JOIN可能比NOT EXISTS更快;反之,右表很大且左表小,NOT EXISTS常有优势 - MySQL 8.0+、PostgreSQL 对这两种写法优化已很成熟,优先选可读性强的,再看执行计划
EXPLAIN
WHERE 子句里别混用 OR 和 NULL 判断
有人想用 WHERE b.id IS NULL OR b.id = 0 来补救,这是错的——LEFT JOIN 后 b.id 为 NULL 表示没匹配,不是业务意义上的“无效值”。混用会漏数据或多数据。
-
IS NULL只用于判断连接失败,不是业务状态标记 - 如果业务里真有
user_id = 0表示“无用户”,那是设计问题,应在 JOIN 条件里排除:ON u.id = o.user_id AND o.user_id != 0 - 千万别写
WHERE b.id = NULL—— 这永远不成立,要用IS NULL
实际写的时候,先确认关联字段是否允许 NULL,再决定用 LEFT JOIN 还是 NOT EXISTS;只要一看到 NOT IN,就条件反射去查子查询会不会返回 NULL。











