优化器放弃is null索引主因是成本估算:null占比高、字段定义为not null、回表开销大、联合索引最左列为is null时失效,或unique索引下null分布不可控;验证需结合explain与count,可force index、analyze table或用生成列绕过。

加了索引但 EXPLAIN 显示 type=ALL 或 key=NULL,不是索引没建好,而是优化器主动放弃了它。
为什么优化器会跳过 IS NULL 的索引
MySQL 不是“不支持”IS NULL走索引,而是它会估算:用索引查一堆 NULL 值,再回表取数据,到底比直接扫全表快不快?这个判断依赖三个现实因素:
-
NULL值占比过高(比如 >70%),优化器认为索引页跳跃访问缓存局部性差,不如顺序读聚簇索引 - 该列虽有单列索引,但
SHOW INDEX中Null列为NO(即字段定义为NOT NULL),此时WHERE col IS NULL永远不匹配,索引必然不被选中 - 查询返回字段多、需要频繁回表,而索引本身又不是覆盖索引,优化器预估回表 I/O 成本 > 全表扫描成本
联合索引里最左列为 IS NULL 就彻底失效
这是物理结构决定的硬限制,不是调优能绕开的。InnoDB 的 B+ 树排序完全依赖最左列的确定值,NULL 不参与比较,也不会写入有序键序列——它被单独挂到叶子节点末尾的 NULL slot list 链表里。
-
INDEX (a, b),查询WHERE a IS NULL AND b = 1→ 优化器无法从根节点开始定位子树,key在EXPLAIN中显示为NULL - 但
WHERE a = 5 AND b IS NULL可以走索引:a = 5先收敛到子树,再在该子树内过滤b IS NULL,key_len会显示两个字段长度 - 别指望
WHERE a IS NULL OR b = 1能用上这个联合索引——OR会让优化器直接放弃复杂路径
UNIQUE 索引对 IS NULL 的特殊处理让优化更难
SQL 标准规定 NULL != NULL,所以 UNIQUE 索引允许无限多个 NULL。这导致优化器丧失两个关键判断依据:
- 无法假设 “
IS NULL最多返回 1 行”,失去快速终止机会 - 无法利用唯一性做等值定位,只能退化为普通二级索引扫描,且因
NULL分布不可控,成本估算更容易失真 - 实测中,哪怕
UNIQUE(col)存在,WHERE col IS NULL仍大概率触发全表扫描,尤其当表里已有几十个NULL行时
怎么验证和临时绕过这个问题
别猜,只信 EXPLAIN;但有时它太保守,得逼它亮底牌:
- 先跑
EXPLAIN SELECT * FROM t WHERE col IS NULL,看key和rows;再对比SELECT COUNT(*) FROM t WHERE col IS NULL,确认实际行数是否真的少 - 强制索引:
EXPLAIN SELECT * FROM t FORCE INDEX (idx_col) WHERE col IS NULL,如果rows显著下降,说明优化器误判了成本 - 更新统计信息:
ANALYZE TABLE t,尤其当表刚批量导入或大量UPDATE后,旧的Cardinality会导致误判 - 终极手段:用生成列绕过,比如
ALTER TABLE t ADD COLUMN col_is_null TINYINT GENERATED ALWAYS AS (col IS NULL) STORED,再给它建普通索引
真正容易被忽略的是:即使 EXPLAIN 显示 key 有值,rows 却接近全表数——这说明索引被选中了,但优化器评估后认为“查完索引还得回表捞太多行”,干脆就按全表扫描的节奏执行。这种“伪命中”比完全不走索引更难察觉。











