is null 查询在mysql中可能不走索引,核心原因是优化器基于成本估算放弃索引:当null值占比超10%~20%,随机i/o代价高于顺序扫描;联合索引中a is null破坏最左前缀匹配,导致索引失效;主键/唯一索引不允许null,故is null在其上直接无效。

IS NULL 查询本身不“禁止”索引,但大量使用时效率变差,核心原因不是语法问题,而是优化器在成本估算中放弃了索引。
为什么 IS NULL 在高占比时基本不走索引?
当某列 NULL 值占比超过 10%~20%,优化器会认为:用索引跳着读页(随机 I/O)比顺序扫数据页更慢。尤其在大表上,rows 接近全表行数、type=ALL 或 key=NULL 就是典型信号。
- MySQL 5.7+ 把
NULL当作最小值存进 B+ 树,但仅限单列二级索引;主键/唯一索引不允许NULL,所以IS NULL在这类索引上直接失效 - 实际执行计划里
rows值没下降,说明优化器没真用索引——别信“建了就有用” -
EXPLAIN中若出现Extra: Using where但没Using index,基本等于白建索引
联合索引中 IS NULL 怎么让整个索引“瘫痪”?
联合索引 INDEX(a, b, c) 要求最左前缀连续匹配,而 a IS NULL 不参与排序比较,B+ 树无法定位分支起点。
-
WHERE a IS NULL AND b = 5:几乎一定跳过该索引,key显示为空 -
WHERE a = 1 AND b IS NULL:可能用到a部分,但b IS NULL不驱动后续列 - 哪怕
a列只有 5% 是NULL,只要查询写了a IS NULL,优化器就倾向放弃整个联合索引
多个字段同时 IS NULL 为什么查询慢得离谱?
MySQL 的 ref_or_null 检索类型只支持单列,无法跨列组合优化。两个字段都写 IS NULL,就会退化成嵌套过滤或全表扫描。
- 实测:350 万行表,
WHERE a = 2 AND (b = 5 OR b IS NULL)0.01 秒;加一个a IS NULL变成(a = 2 OR a IS NULL) AND (b = 5 OR b IS NULL),耗时飙升到 81 秒 - 优化器只能选一个单列索引(比如
idx_b),先扫出所有b IS NULL行,再逐行判断a条件——本质是回表 + 过滤 - 换成默认值(如
0或'')后,WHERE a IN (2, 0) AND b IN (5, 0)立刻回到毫秒级
绕过 IS NULL 索引陷阱的实操底线
真正有效的做法不是调优查询,而是重构数据语义和索引结构。
- 高频
IS NULL查询字段,优先考虑补默认值并加NOT NULL约束——这是最省事且效果最稳的方案 - 必须保留
NULL语义时,用生成列建索引:ALTER TABLE t ADD COLUMN c_nonnull VARCHAR(255) STORED AS (COALESCE(c, '_NULL_'));,再对c_nonnull建索引 -
FORCE INDEX只能验证是否误判,不能解决根本问题;如果强制后rows没明显下降,说明就是NULL比例太高或索引设计错位 - UNIQUE 索引对
IS NULL更不友好——允许多个NULL导致基数统计失真,优化器更不敢用
IS NULL 是否走索引从来不是布尔判断,而是优化器在“随机读页代价”和“顺序扫描代价”之间反复权衡的结果。业务层看到的是慢查询,底层其实是 I/O 模式切换失败。











