is not null 查询可能走索引,也可能不走——取决于优化器基于成本的判断:当null值占比低、查询列被索引覆盖或为复合索引前导列时易走索引(type为ref/range);否则因回表开销大而退化为全表扫描(type为all)。

IS NOT NULL 查询到底走不走索引?先看执行计划
它可能走,也可能不走——关键不是语法本身,而是 MySQL 优化器算出来的成本够不够小。用 EXPLAIN 看执行计划时,如果 type 是 ref 或 range、Extra 出现 Using index,说明走了索引;如果 type 是 ALL,那就是全表扫描了。
常见误判来源:有人看到 SELECT * + IS NOT NULL 没走索引,就断言“IS NOT NULL 让索引失效”,其实真正卡住的是回表开销——你要的字段不在索引里,MySQL 得拿着索引里的主键去聚簇索引里逐条捞完整行,当预估要捞的行数太多,它宁愿直接扫聚簇索引。
- 只查索引列(覆盖索引):
SELECT name FROM t_user WHERE name IS NOT NULL更容易走索引 - 查 * 或含非索引列:
SELECT * FROM t_user WHERE name IS NOT NULL即使有索引,也常退化为全表扫描 - NULL 值占比极低时(比如 0.1%),
IS NOT NULL匹配行数多,回表代价高 → 不走索引 - NULL 值占比极高(比如 95%),
IS NOT NULL实际只匹配少量行 → 很可能走索引
为什么 NULL 值在 B+ 树里总在最左边?
InnoDB 把 NULL 视为“该字段可能的最小值”,所以所有 NULL 值的二级索引记录都排在 B+ 树叶子节点最左侧。这个设计让 IS NULL 可以用范围扫描(range),但对 IS NOT NULL 来说,它要跳过最左一截、扫描剩余全部,天然覆盖更大区间。
也就是说:IS NULL 是一个确定的左边界点查询;而 IS NOT NULL 是一个“从第一个非 NULL 值开始往右全扫”的逻辑 —— 扫得越宽,优化器越倾向放弃索引。
-
IS NULL对应的扫描区间是[NULL, NULL](单点) -
IS NOT NULL对应的扫描区间是(NULL, +∞)(开区间,实际长度取决于数据分布) - 复合索引中,如果
IS NOT NULL作用于前导列(如INDEX (a, b)上WHERE a IS NOT NULL),仍可利用索引定位起始位置;但如果作用于非前导列(如WHERE b IS NOT NULL),则无法使用该复合索引
什么时候 IS NOT NULL 明明有索引却强制全表扫描?
典型触发场景不是语法错误,而是优化器主动降级——它对比了两种路径的成本:
- 走二级索引 + 回表:读 N 个索引页 + N 次随机 I/O 去聚簇索引取整行
- 直接扫聚簇索引:顺序读 M 个数据页(M ≈ 表总行数 × 平均行大小 / 页大小)
- 当 N 太大(比如占表 30% 以上),顺序读反而比大量随机 I/O 更快,优化器就选
ALL - 可通过
SELECT @@last_query_cost查看当前语句预估成本,对比不同写法的数值差异 - MySQL 8.0+ 支持
ANALYZE TABLE更新统计信息,旧统计可能导致成本误判,尤其在大批量 INSERT/DELETE 后
想让 IS NOT NULL 稳定走索引,绕不开这三点
别指望加个索引就万事大吉。真正起效的操作很具体:
- 用覆盖索引:把
WHERE和SELECT涉及的字段全包进索引,例如INDEX (name, age)配合SELECT name, age FROM t WHERE name IS NOT NULL - 控制 NULL 比例:业务上能
NOT NULL约束的字段,就别留默认NULL;真需要空值语义,考虑用特殊值(如''、-1)替代,避免破坏索引分布 - 必要时强制索引:仅限已确认成本计算失准的场景,用
FORCE INDEX(idx_name),但得同步监控后续数据增长是否让该策略反噬
最常被忽略的一点:IS NOT NULL 是否走索引,和字段是否允许 NULL(即建表时有没有 DEFAULT NULL)无直接关系,只和实际存储的 NULL 值数量、查询返回字段、索引结构三者共同决定。盯着 DDL 看不出问题,得看 EXPLAIN 和真实数据分布。











