在mysql的查询逻辑中,not in操作符用于判断某个字段的值是否不在指定的值列表或者子查询返回的结果集中,是数据过滤场景下的常用工具。合理使用not in可以快速筛选出符合排除条件的记录,但如果不了解其底层逻辑和限制,很容易出现查询结果错误或者性能问题。

not in的基础语法
not in的使用分为两种常见形式,一种是直接指定值列表,另一种是搭配子查询使用。
指定值列表的语法
当需要排除固定的几个值时,可以直接在not in后面跟上括号包裹的值列表,语法格式如下:
-- 查询用户表中年龄不在18、20、25这三个值的用户记录 SELECT * FROM user WHERE age NOT IN (18, 20, 25);
搭配子查询的语法
当需要排除的值来自另一个查询的结果时,可以在not in后面跟上子查询语句,语法格式如下:
-- 查询没有下单的用户,即用户id不在订单表的user_id集合中 SELECT * FROM user WHERE id NOT IN (SELECT user_id FROM order_table);
not in的使用注意事项
使用not in时需要特别注意以下几个容易踩坑的点,否则会导致查询结果不符合预期。
null值的影响
如果not in后面的值列表或者子查询结果中包含null值,那么整个not in的判断结果会返回null,最终不会匹配到任何记录。这是因为mysql中任何值和null比较的结果都是null,而where条件只保留结果为true的记录。
比如下面的查询,因为子查询返回的结果包含null,所以最终不会返回任何数据:
-- 子查询返回的结果包含null,整个not in判断结果为null,无匹配记录 SELECT * FROM user WHERE id NOT IN (SELECT user_id FROM order_table WHERE user_id IS NULL OR user_id = 1);
要避免这个问题,需要在子查询中提前过滤掉null值:
-- 子查询中过滤null值,保证not in的集合里没有null SELECT * FROM user WHERE id NOT IN (SELECT user_id FROM order_table WHERE user_id IS NOT NULL);
字段类型匹配问题
not in前后的字段类型需要保持一致,如果类型不匹配,mysql会进行隐式类型转换,可能导致索引失效,甚至结果错误。比如字段是字符串类型,值列表里写数字,就可能触发不必要的类型转换。
not in的替代方案
在某些场景下,not in的性能或者逻辑表现不如其他写法,可以考虑以下替代方案。
使用not exists替代
当not in搭配子查询使用时,如果子查询结果集较大,not in的性能往往不如not exists,而且not exists不受null值的影响,逻辑更稳妥。
-- 用not exists改写之前的查询,逻辑更清晰且不受null影响 SELECT u.* FROM user u WHERE NOT EXISTS (SELECT 1 FROM order_table o WHERE o.user_id = u.id);
使用left join结合is null替代
也可以通过左连接的方式实现相同的排除逻辑,适合需要同时获取关联表字段的场景。
-- 左连接后筛选关联表id为null的记录,即没有对应订单的用户 SELECT u.* FROM user u LEFT JOIN order_table o ON u.id = o.user_id WHERE o.user_id IS NULL;
不同方案的适用场景对比
以下是not in、not exists、left join三种方案的适用场景对比:
| 方案 | 适用场景 | 注意事项 |
|---|---|---|
| not in | 排除固定值列表、子查询结果无null且数据量小 | 必须确保值集合无null,字段类型匹配 |
| not exists | 子查询结果集大、可能存在null值 | 逻辑更严谨,性能通常更优 |
| left join + is null | 需要同时获取关联表字段、排除关联不存在的记录 | 需要注意关联条件的唯一性,避免重复记录 |