Anti Join 是数据库优化器对“左表有、右表无”逻辑的底层执行策略,并非标准SQL语法;LEFT JOIN WHERE IS NULL 是其常用写法,兼容性好但依赖右表连接字段非空、等值条件及索引支持,而 NOT EXISTS 更安全且同样可被优化为 Anti Join。

什么是 Anti Join,它和 LEFT JOIN WHERE IS NULL 有什么区别
Anti Join 不是 SQL 标准语法里的独立关键字,而是数据库优化器对「查找左表有、右表无」这类逻辑的底层执行策略。你写的 LEFT JOIN ... WHERE right_table.id IS NULL,在 PostgreSQL、SQL Server 或 Oracle 中很可能被优化成真正的 Anti Join(比如 Hash Anti Join),但 MySQL 8.0.18 之前压根不支持该计划,只能走嵌套循环+过滤。
所以别纠结“怎么写 Anti Join”,重点是:怎么写出能被高效执行的不匹配查询。
最稳妥的写法:LEFT JOIN + IS NULL(兼容所有主流数据库)
这是实际项目中最推荐的方式——语义清晰、可读性强、兼容性好。只要注意右表连接字段不能为 NULL,就能避免误判。
- 确保
right_table.join_key是NOT NULL字段,否则IS NULL会漏掉本应匹配却因 NULL 值失败的记录 - 连接条件必须用等值(
=),不能用!=或函数包装左/右字段,否则无法走索引,也大概率无法触发 Anti Join 优化 - 示例:查所有没有订单的用户
SELECT u.* FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.user_id IS NULL;
注意这里用的是 o.user_id IS NULL,不是 o.id IS NULL——因为 o.id 是主键,不可能为 NULL;而 o.user_id 是外键,若没匹配上才为 NULL,这才是判断依据。
替代方案:NOT EXISTS 比 NOT IN 更安全
NOT IN 看似简洁,但只要右表连接字段存在任意一个 NULL,整条查询就返回空结果——这是新手最容易踩的坑。
-
NOT EXISTS不受 NULL 影响,语义更贴近“不存在匹配行”,且多数引擎能将其优化为 Anti Join - 子查询中必须关联外层表(如
WHERE o.user_id = u.id),否则变成全表扫描 - MySQL 8.0+ 对
NOT EXISTS的优化已相当成熟,PostgreSQL 默认就优先选它
SELECT * FROM users u WHERE NOT EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.id );
哪些情况会让 Anti Join 失效或退化
即使写了正确的 LEFT JOIN + IS NULL,也可能因以下原因被迫走低效执行计划:
- 右表连接字段缺少索引(如
orders.user_id无索引),优化器可能放弃 Hash/Merge Anti Join,改用 Nested Loop + 全表扫描 - 连接字段类型不一致(如
users.id是BIGINT,orders.user_id是VARCHAR),隐式转换导致索引失效 - WHERE 条件里混入对右表的非连接字段过滤(如
AND o.status = 'shipped'),会让优化器无法直接使用 Anti Join,转而先 JOIN 再过滤
真正决定性能的从来不是写法名称,而是连接字段的索引、类型一致性,以及是否引入破坏半连接语义的额外条件。

















