mysql中is null和is not null查询可以走索引,但是否真正使用取决于字段是否允许null、查询写法及数据分布;根本原因是null不参与b+树有序比较,innodb将其特殊存储于叶子页最左或空值链表,导致优化器基于成本估算决定是否走索引。

MySQL 中 IS NULL 和 IS NOT NULL 查询可以走索引,但是否真走、走得多不多,取决于字段定义、查询写法和数据分布——不是“一定不走”,也不是“加了索引就高效”。
为什么 IS NULL 有时不走索引?
根本原因在于 MySQL 对 NULL 的存储和索引结构的处理方式。B+ 树索引默认包含 NULL 值(InnoDB 允许索引列含 NULL),但只有当该列在表定义中允许为 NULL(即含 DEFAULT NULL 或没写 NOT NULL)时,IS NULL 才可能命中索引。
- 如果字段定义为
NOT NULL,WHERE col IS NULL会被优化器直接判定为恒假,EXPLAIN显示Impossible WHERE,不查表也不走索引 - 如果字段允许
NULL,但查询里用了SELECT *或包含非索引列,即使IS NULL走了索引,仍要回表取数据,IO 成本高 -
IS NULL在复合索引中只能用于最左前缀的首个可空列;若(a, b)索引中a允许NULL,WHERE a IS NULL AND b = 1可用索引;但WHERE b IS NULL就完全无法利用该索引
IS NOT NULL 的索引行为更稳定,但有隐性陷阱
IS NOT NULL 比 IS NULL 更容易触发索引扫描(type: range),尤其在字段允许 NULL 且有索引时。但它不等于“高性能”:
- 当该字段
NULL比例极低(比如 -
SELECT *+IS NOT NULL依然会回表;若只查索引覆盖的列(如SELECT id, email FROM users WHERE email IS NOT NULL),且索引是INDEX(email)或更优的INDEX(email, id),就能避免回表 - 唯一索引(
UNIQUE)对IS NOT NULL无额外帮助——因为UNIQUE允许多个NULL,它不约束非空值的唯一性逻辑
真正有效的优化手段
别只盯着加单列索引。实际生效的优化往往组合出现:
- 把常用过滤字段设为
NOT NULL并给默认值(如email VARCHAR(100) NOT NULL DEFAULT ''),把业务上的“空”转为确定字符串,WHERE email != ''就能稳定走索引 - 建覆盖索引:例如高频查
WHERE status IS NULL ORDER BY created_at,就建INDEX(status, created_at, id)(把排序和主键也包进去),让整个查询在索引层完成 - 避免在
IS NULL字段上做函数操作,比如WHERE IFNULL(email, '') = ''会强制全表扫描——IFNULL让索引失效 - 对超大表且
NULL比例高的场景,考虑分区:按email IS NULL拆成两个子表(或用 LIST 分区),物理隔离后查询范围大幅缩小
最易被忽略的一点:ANALYZE TABLE 必须定期执行。统计信息过期时,优化器对 NULL 值分布的估算严重失真,会导致本该走索引的 IS NOT NULL 查询被误判为全表扫描。这不是配置问题,是运维动作。











