NOT IN 在 MySQL 中易变慢,因其遇 NULL 返回 UNKNOWN 且难索引下推;应改用 LEFT JOIN + IS NULL,确保右表连接字段有索引、非空,并将子查询条件移至 ON 子句。

为什么 NOT IN 在 MySQL 里容易变慢
因为 NOT IN 子查询遇到 NULL 值时会整个表达式返回 UNKNOWN,结果集直接为空——这不是 bug,是 SQL 标准行为。但更关键的是,MySQL 很难对 NOT IN 做有效索引下推,尤其当子查询结果多、无索引或含 NULL 时,常退化成嵌套循环+全表扫描。
用 LEFT JOIN + IS NULL 替代的写法要点
核心思路:把“不在集合中”转为“左表有、右表没匹配上”。必须确保右表连接字段有索引,且显式过滤掉 NULL 干扰。
- 连接条件要用
=,不能用IN或函数包裹字段 - 右表的连接字段必须
NOT NULL或在ON子句中提前排除NULL(例如ON t2.id = t1.ref_id AND t2.id IS NOT NULL) -
WHERE条件里必须写t2.id IS NULL,不能写t2.id = NULL(后者永远不成立) - 如果原
NOT IN子查询带WHERE过滤,要把它移到LEFT JOIN的ON里,而不是WHERE,否则会把外连接变成内连接
示例对比:
/* 慢的 NOT IN */ SELECT * FROM orders WHERE customer_id NOT IN ( SELECT id FROM customers WHERE status = 'inactive' );
/* 快的 LEFT JOIN 写法 */ SELECT o.* FROM orders o LEFT JOIN customers c ON o.customer_id = c.id AND c.status = 'inactive' WHERE c.id IS NULL;
哪些情况 LEFT JOIN 也未必快
不是所有 NOT IN 都适合硬改。以下情况要小心:
- 右表结果集极小(比如就几条),
NOT IN可能被优化器转成常量展开,反而比JOIN更轻量 - 右表连接字段没索引,
LEFT JOIN会触发全表扫描+临时表,性能可能更差 - 原查询本身有
DISTINCT或聚合,强行改JOIN可能引入重复行,得加GROUP BY或DISTINCT补救 - MySQL 5.6 以前版本对
LEFT JOIN ... IS NULL的索引使用不稳,建议先EXPLAIN看type是否为ref或range
验证是否真的生效的三个动作
改完别急着上线,立刻做这三件事:
- 用
EXPLAIN FORMAT=TREE(MySQL 8.0+)或EXPLAIN看执行计划,确认右表走了索引,且没有Using temporary或Using filesort - 在测试库用
SELECT SQL_NO_CACHE ...跑两遍,对比NOT IN和LEFT JOIN版本的真实执行时间 - 检查结果行数是否一致——特别是右表含
NULL时,NOT IN返回空,而LEFT JOIN版本不会受其影响,这点恰恰是优势,但也意味着语义已不同
真正卡住性能的往往不是语法本身,而是连接字段有没有索引、NULL 怎么处理、以及优化器是否识破你的意图。改写只是第一步,验证才是关键。



















