MySQL中IS NULL能走索引,前提是字段允许NULL且有B+树索引;若定义为NOT NULL则优化器直接忽略该条件。

MySQL 中 IS NULL 能走索引吗?看字段是否允许 NULL
能,但前提是该列**没有被定义为 NOT NULL**。如果字段声明为 NOT NULL,优化器会直接忽略 IS NULL 条件(因为永远不成立),自然不走索引;反之,若字段允许 NULL,且有 B+ 树索引,IS NULL 通常可命中索引的最左前缀部分。
常见错误现象:EXPLAIN 显示 type=ALL 或 key=NULL,其实是字段被误设为 NOT NULL,或索引未覆盖查询所需列(导致回表后优化器放弃使用)。
- 检查字段定义:
SHOW CREATE TABLE t1看col_name是否含NOT NULL - 确保索引存在且非冗余:单列索引
INDEX idx_col (col_name)即可支持col_name IS NULL - 复合索引中,
IS NULL只对最左连续非 NULL 列生效;例如索引(a, b, c),WHERE a = 1 AND b IS NULL可用,但WHERE b IS NULL不可用
PostgreSQL 对 IS NULL 的索引支持更“诚实”
PostgreSQL 默认**不把 NULL 值存入普通 B-tree 索引**(除非显式声明 NULLS NOT DISTINCT 或建表达式索引),所以 IS NULL 查询默认走不了索引 —— 这不是 bug,是设计选择。
使用场景:当需要高频查 NULL 值时,必须主动适配:
- 建部分索引:
CREATE INDEX idx_col_null ON t1 (id) WHERE col_name IS NULL - 或用表达式索引:
CREATE INDEX idx_col_isnull ON t1 ((col_name IS NULL)),然后写WHERE (col_name IS NULL) = true - 注意:普通索引 +
ORDER BY col_name NULLS FIRST才会让 NULL 进入索引排序逻辑,但不等于支持IS NULL过滤
SQLite 的 IS NULL 索引行为容易被低估
SQLite 的 B-tree 索引**默认包含 NULL 值**,且 IS NULL 可以走索引 —— 但仅限于**最左列**,且不能跳过前导列。它的限制不在“支不支持”,而在查询写法是否触发最左匹配。
性能影响明显:没索引时全表扫描,有索引但写成 WHERE col2 IS NULL(而索引是 (col1, col2))就完全失效。
- 验证方式:
EXPLAIN QUERY PLAN SELECT * FROM t1 WHERE col1 = 'x' AND col2 IS NULL,看输出是否含SEARCH - 避免陷阱:不要依赖
OR col2 IS NULL触发索引;SQLite 对 OR 优化弱,大概率退化为全表扫描 - 小版本差异:3.8.0+ 对
IS NULL的索引识别更稳定,旧版可能静默忽略
为什么 ORM 自动生成的 IS NULL 查询经常不走索引?
不是数据库的问题,是 ORM 构建条件时引入了隐式类型转换或包装,让优化器无法识别原始谓词。比如 Django 的 filter(col__isnull=True) 在某些配置下会生成 col IS NULL OR col != col(用于兼容 NaN 场景),直接废掉索引。
真实案例中,排查路径比改代码还关键:
- 抓实际执行 SQL:
SHOW FULL PROCESSLIST(MySQL)、pg_stat_statements(PG)、或启用 SQLite 的PRAGMA compile_options+ 日志 - 检查 ORM 输出是否带函数包裹,如
COALESCE(col, '') IS NULL—— 这种必然不走索引 - 参数绑定本身不影响索引选择,但若 ORM 把
None绑定为字符串'NULL',就变成字符匹配,彻底偏离语义
真正麻烦的是跨库抽象层:一个 is_null() 方法在不同方言里展开逻辑不同,底层引擎又不互通,最后只能靠每个环境单独验 EXPLAIN。没人替你担这个心,得自己看执行计划。

















