IS NULL 查询可能走索引但不保证,取决于NULL占比、索引类型及优化器估算;IS NOT NULL 几乎从不走索引,因B+树无法高效反向跳过NULL;联合索引中NULL字段应靠右或剔除,避免破坏最左前缀。

IS NULL 查询为什么有时不走索引
MySQL 的 IS NULL 查询**可能走索引,但不保证**——它取决于 NULL 值占比、索引类型、MySQL 版本(5.7+ 支持增强)和优化器成本估算。InnoDB 的 B+ 树把 NULL 当作特殊标记值,不参与常规排序,所以优化器常认为“扫一遍索引再过滤”不如直接全表扫描快。
- 高 NULL 比例(如 > 30%)时,
EXPLAIN显示type=ALL或key=NULL是常态,不是 bug - 单列索引上
IS NULL在 8.0.13+ 更可能命中,但联合索引里只要最左列是IS NULL,整个前缀就断了(例如INDEX(a,b),WHERE a IS NULL AND b = 1基本不用索引) -
UNIQUE索引允许多个NULL,反而让优化器更难做等值定位,IS NULL在唯一索引上通常也不走
IS NOT NULL 几乎等于放弃索引
IS NOT NULL 在绝大多数场景下**不会走索引**,哪怕该列有 INDEX 或 UNIQUE INDEX。原因很直接:它本质是“排除所有 NULL”,而 B+ 树无法高效反向跳过一个特殊标记值;优化器干脆按全表扫描处理,rows 等于表总行数,Extra 里只写 Using where,没有 Using index。
- 别依赖
EXPLAIN里显示 “有索引可用” 就以为能用——关键看key是否非空、rows是否显著下降 - 想绕过这个问题?要么改用
NOT NULL+ 默认值(如DEFAULT ''),要么建生成列:ALTER TABLE t ADD COLUMN email_filled TINYINT GENERATED ALWAYS AS (email IS NOT NULL) STORED,再给它加索引 -
FORCE INDEX强制也救不了IS NOT NULL,它只会让执行计划更糟(额外回表 + 过滤)
联合索引里 NULL 字段放错位置会拖垮整个索引
联合索引 (a, b, c) 要生效,必须满足最左前缀原则。但如果 b 列 60% 是 NULL,那么 WHERE a = ? AND b IS NOT NULL 很可能只用到 a 部分,b 条件退化为 post-filter —— 因为 NULL 打乱了 b 在 B+ 树里的有序分布,优化器不敢信任索引顺序。
- 高频查询条件含
IS NULL/IS NOT NULL的字段,**不要放在联合索引中间**,尽量靠右,或直接剔除 - 如果该字段只用于
IS NULL且结果集很小(比如 0.1% 行),考虑单独建单列索引,而不是塞进联合索引 - 用
COALESCE(b, -1)改写查询能“骗过”索引,但函数会导致普通索引失效——除非你建的是函数索引:CREATE INDEX idx_b_coal ON t ((COALESCE(b, -1)))(MySQL 8.0.13+)
NULL 值本身就在悄悄吃掉索引空间和性能
每个允许 NULL 的索引列,InnoDB 都会在每条索引记录里多占 **1 字节空值标记位**。看起来不多,但百万级表意味着索引页更少容纳键值、B+ 树层级变深、I/O 次数上升。更麻烦的是,NULL 参与比较永远返回 UNKNOWN,导致 !=、NOT IN、COUNT(col) 等行为和直觉不符,查不到数据还找不到原因。
-
key_len突然变大 1?先查SHOW INDEX看Null列是不是YES -
NOT IN (SELECT ...)子查询只要有一行是NULL,整个结果就是空——换成NOT EXISTS或提前WHERE col IS NOT NULL - 业务上“可选但非必须”的字段(比如用户头像 URL),与其留
NULL,不如设DEFAULT ''并加NOT NULL,索引干净、查询简单、存储还略省
NOT NULL。


















