not in子查询含null时delete完全失效,因条件判为unknown被where过滤,且索引无法下推;应改用delete t1 from t1 left join t2 on t1.id = t2.id where t2.id is null。

NOT IN 子查询含 NULL 时 DELETE 直接失效
DELETE 语句里用 WHERE id NOT IN (SELECT id FROM t2),只要 t2.id 中任意一行是 NULL,整个条件就判为 UNKNOWN,而 SQL 标准规定:WHERE 只保留 TRUE 行,UNKNOWN 和 FALSE 都被过滤——结果就是一条不删,且优化器无法安全下推索引,大概率放弃使用 id 上的索引。
MySQL 优化器不敢对 NOT IN 下推索引
即使 t2.id 全非空、也有索引,MySQL(尤其 5.7 及更早)仍常退化为全表扫描,因为 NOT IN 的谓词方向不可下推:它本质要求“确认不在集合中”,而索引擅长的是“快速定位存在”,不是“穷举排除”。EXPLAIN 中常见 type: ALL、key_len: NULL、Extra: Using where; Using join buffer,Handler_read_rnd_next 动辄几百万次,就是典型信号。
用 LEFT JOIN + IS NULL 替代 NOT IN 的实操要点
正确改写不是简单套语法,得盯住三个关键点:
- 右表连接字段必须显式过滤
NULL,比如写成(SELECT id FROM t2 WHERE id IS NOT NULL),否则LEFT JOIN后IS NULL会把左表匹配到NULL的行也留下,逻辑错位 - 连接条件必须是纯等值,不能带函数或类型转换,例如
ON a.uid = b.uid可以,ON a.uid = CAST(b.uid AS CHAR)就可能失索引 - 左表和右表的连接字段类型、字符集要一致;若右表是
VARCHAR而左表是INT,隐式转换会让索引失效 - 示例原语句:
DELETE FROM t1 WHERE id NOT IN (SELECT id FROM t2);安全改写:DELETE t1 FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.id IS NULL
为什么加了索引,NOT IN 还是慢
索引对 NOT IN 几乎无效,根本原因不是“没建对”,而是执行逻辑决定的:
-
IN可走INDEX RANGE SCAN,查几个值很快 -
NOT IN没法用范围跳过,只能扫完整个索引甚至回表比对每行,等价于INDEX FULL SCAN或更糟的全表扫描 - 子查询若没加
WHERE过滤、返回行数多,优化器直接放弃索引,选最保守的嵌套循环 - 线上遇到 DELETE 慢 + 查不到预期数据,第一反应不该是“加索引”,而是检查子查询是否吐了
NULL、立刻切到LEFT JOIN写法
id IS NULL 导致的语义坍塌和执行计划失控。改写时漏掉 WHERE ... IS NOT NULL 或字段类型不一致,LEFT JOIN 也会白忙一场。










