not exists比left join + is null更快,因其采用半连接语义,找到匹配即短路退出,不构造中间结果集,且仅依赖users.id索引查找,避免全表扫描和临时表;而left join需生成完整连接结果再过滤is null,易因数据倾斜或缺失索引导致性能骤降。

EXISTS 为什么比 LEFT JOIN + IS NULL 更快查外键缺失
主表有 100 万条记录,而外键字段(比如 user_id)大量指向不存在的 users 表记录时,用 LEFT JOIN ... WHERE t2.id IS NULL 常会触发全表扫描和临时表,尤其当 users.id 缺少索引或数据倾斜严重时。而 EXISTS 能利用半连接(semi-join)语义:只要子查询找到一个匹配就短路退出,不构造中间结果集。
-
EXISTS是布尔判断,引擎只关心“是否存在”,不取字段、不去重、不排序 - 即使子查询里写
SELECT 1、SELECT *或SELECT id,执行计划通常完全一样 - 如果
users(id)有索引,EXISTS一般走索引查找(Index Seek),而非全索引扫描
正确写法:用 NOT EXISTS 找主表中“孤儿记录”
要找出 orders 表里所有 user_id 在 users 表中不存在的行,必须用 NOT EXISTS,不是 EXISTS:
SELECT o.* FROM orders o WHERE NOT EXISTS ( SELECT 1 FROM users u WHERE u.id = o.user_id );
常见错误:
- 写成
EXISTS (SELECT 1 FROM users u WHERE u.id = o.user_id)→ 找的是“有对应用户”的订单,和需求相反 - 子查询里漏掉关联条件,比如写成
WHERE u.id = 123→ 变成固定值判断,失去关联意义 - 忘记在
users.id上建索引 →NOT EXISTS退化为对每条orders行都扫一遍users全表
对比 IN 和 NOT IN:NULL 值会让 NOT IN 直接失效
如果 users.id 列允许 NULL,下面这句永远返回空结果:
SELECT * FROM orders WHERE user_id NOT IN (SELECT id FROM users);
因为 SQL 中任意值与 NULL 做 != 或 NOT IN 比较,结果都是 UNKNOWN,被当作假值过滤掉。而 NOT EXISTS 完全不受 NULL 影响——它只看子查询是否返回行。
-
NOT IN要求子查询结果集不能含NULL,否则需额外加WHERE id IS NOT NULL -
NOT EXISTS天然安全,语义清晰,推荐作为默认选择
性能关键:确保被查字段有索引且类型一致
NOT EXISTS 的效率高度依赖子查询中被关联字段的索引和类型匹配:
- 检查
users.id是否有索引:SHOW INDEX FROM users WHERE Column_name = 'id'; - 确保
orders.user_id和users.id类型完全一致(比如都是BIGINT,而不是一边INT一边VARCHAR) - 如果
user_id是字符串但存了数字(如'123'),而users.id是整数,隐式转换会导致索引失效 - 复合索引没用——这里只需要单列索引,
users(id)就够了
外键约束本身不等于索引,哪怕你加了 FOREIGN KEY (user_id) REFERENCES users(id),也得手动给 users.id 加索引,否则 NOT EXISTS 还是慢。










