not in 遇 null 返回空结果,in 则忽略 null;类型不匹配会导致隐式转换和索引失效;超 500 项 in 建议改用临时表或分批处理。

MySQL 中 IN 和 NOT IN 的实际行为差异
别默认它们是对称的反操作——NOT IN 遇到 NULL 会直接返回空结果,而 IN 不会。这是最常踩的坑:查不到数据不是逻辑写错了,是字段里混了 NULL。
-
IN是“任意匹配即为真”,遇到NULL会被忽略(比如1 IN (1, NULL)返回1) -
NOT IN是“所有都不匹配才为真”,只要右侧集合含NULL,整个表达式恒为UNKNOWN,等价于不满足,结果集为空 - 典型错误场景:用子查询做
NOT IN,而子查询字段允许NULL(如SELECT * FROM a WHERE id NOT IN (SELECT user_id FROM b),若b.user_id有NULL,这条语句就查不出任何东西)
替代 NOT IN 的安全写法
想排除一批 ID,又不确定子查询会不会带出 NULL,就别硬扛 NOT IN。用 NOT EXISTS 或 LEFT JOIN ... IS NULL 更稳。
-
NOT EXISTS不受NULL影响:SELECT * FROM a WHERE NOT EXISTS (SELECT 1 FROM b WHERE b.user_id = a.id) -
LEFT JOIN写法更直观:SELECT a.* FROM a LEFT JOIN b ON a.id = b.user_id WHERE b.user_id IS NULL - 性能上,
NOT EXISTS通常比NOT IN快,尤其子查询结果大时;LEFT JOIN在有合适索引时也很快,但要注意避免SELECT *带出大量无效字段
IN 列表长度与性能边界
MySQL 对 IN 列表没硬性上限,但超过几百项后,解析、优化、执行成本明显上升,还可能触发临时表或全表扫描。
- 50 项以内:放心用,优化器能走索引
- 50–500 项:建议拆成多个批次查,或改用临时表 +
JOIN - 超 500 项:基本该换方案了——建临时表插入值,再
JOIN;或者用程序分批请求,避免单条 SQL 过重 - 注意:
IN后面跟子查询时,如果子查询返回上万行,MySQL 5.7+ 会自动转成物化临时表,但依然可能慢;8.0+ 有更好优化,但别依赖
字符串字段用 IN 的隐式转换陷阱
当 IN 左侧是字符串字段(比如 status VARCHAR(20)),右侧却传数字(如 WHERE status IN (0, 1)),MySQL 会把字符串字段强制转成数字比较,导致索引失效。
- 现象:
EXPLAIN显示type: ALL,哪怕status上建了索引 - 原因:
'active'转成数字是0,'done'也是0,所有非数字开头的字符串都变成0,没法走范围或等值索引 - 解决:确保类型一致——
WHERE status IN ('0', '1'),或者用CAST(status AS SIGNED)(但会丢索引)
实际用的时候,NULL 和类型匹配这两个点最容易被跳过。一查没结果,先看子查询有没有 NULL;一查变慢,先确认 IN 里是不是混了数字和字符串。










