mysql中null是缺失标记而非值,故b+树索引不存储null字面量,而是将其归入叶子节点末尾的无序null slot list链表;is null查询仅在单列索引或联合索引最左连续等值后的首列可走索引,高null占比(>20%)时优化器常弃用索引。

MySQL 对 NULL 的索引处理和查询行为,核心在于它不是“值”,而是“缺失标记”。这直接导致常规 B+Tree 索引无法像处理普通值那样高效定位 NULL,但也不是完全不能用——关键看场景、索引类型和优化器判断。
NULL 在 B+Tree 索引中怎么存?
InnoDB 把 NULL 当作特殊状态单独管理:
- 索引键(index key)本身不存储
NULL字面量,对应记录的键值部分被设为空 - 所有该列值为
NULL的索引条目,被统一挂到叶子节点末尾一个叫 “NULL slot list” 的无序链表里 - 这个链表按插入顺序维护,不排序,所以
ORDER BY col配合IS NULL无法利用索引排序 - 唯一索引允许多个
NULL,因为校验时直接跳过该列——这不是 bug,是 SQL 标准要求
哪些查询能走索引?哪些不能?
能否走索引,不取决于语法写法是否“合法”,而取决于优化器对成本的评估和索引结构是否支持快速定位:
-
WHERE col = 'xxx'(非 NULL 值):只要索引存在且选择性好,基本走索引 -
WHERE col IS NULL:可能走索引,但仅限于单列索引或联合索引的最左前缀列;若该列NULL占比高(如 >20%),优化器常放弃索引改全表扫描 -
WHERE col IS NOT NULL:MySQL 5.7+ 多数情况可走索引,但仍受基数统计影响,不能绝对依赖 -
WHERE a IS NULL AND b = 'x'(a 是联合索引最左列):不走索引,因a IS NULL无法提供范围边界,破坏最左前缀匹配 -
WHERE a = 1 AND b IS NULL(a 是最左列):可以走索引,a 定位子树后,b 的IS NULL可在该子树内查 NULL slot list
想让 IS NULL 查询变快,怎么办?
靠原生索引“碰运气”不可靠,推荐确定性方案:
- 加生成列 + 普通索引:
ALTER TABLE t ADD COLUMN col_is_null TINYINT STORED AS (col IS NULL);CREATE INDEX idx_col_null ON t(col_is_null);查询改写为:WHERE col_is_null = 1 - 函数索引(MySQL 8.0.13+)也可用,例如:
CREATE INDEX idx_col_null_func ON t((col IS NULL));但注意:函数索引仍不索引NULL本身,而是索引表达式结果(TRUE/FALSE) - 避免在联合索引中间列允许
NULL,否则会中断后续列的索引下推能力
设计阶段就该规避的问题
很多性能问题其实在建表时就能预防:
- 业务上必填字段,一律
NOT NULL+ 合理默认值(如0、''、'unknown') - 可选字段慎用
NULL,优先考虑语义清晰的哨兵值(如状态字段用-1表示“未设置”) - 高频查
IS NULL的字段(如日志表的processed_at),从一开始就要规划生成列方案 - 不要依赖
UNIQUE约束来“过滤NULL”,它本就不做这事;真要约束,得用生成列兜底或应用层拦截











