MySQL中NULL值不参与B+树排序,而是挂入叶子节点末尾的无序NULL slot list;IS NULL查询能否走索引取决于NULL比例与优化器成本估算,高比例时(>20%)常退化为全表扫描。

MySQL中NULL值在B+树索引里根本不参与排序比较
不是“存得不好”,而是根本没按常规方式存——NULL不进入B+树的有序键值序列。InnoDB对每个索引记录做键值拼接时,只要某列为NULL,该列就不参与键值构造;整条记录的索引键可能被截断(如联合索引(a, b)中a=1, b=NULL只存(1)),甚至完全跳过排序路径,直接挂到叶子节点末尾的NULL slot list链表里。
这意味着:WHERE col = 1能走B+树二分查找,但WHERE col IS NULL查的是那个无序链表,没法用树结构加速定位。
IS NULL查询能否走索引,取决于NULL比例和优化器成本估算
别信“一定不走”或“肯定能走”的说法。实际是否使用索引,由优化器根据统计信息动态决定:
-
EXPLAIN显示type: ref_or_null说明走了索引,但会额外扫描NULL slot list,性能弱于纯ref - 若该列
NULL占比超过约20%,优化器常认为遍历链表+回表成本 > 全表扫描,直接选type: ALL - MySQL 5.7+对单列索引的
IS NULL支持增强,但联合索引仍严格遵循最左前缀:一旦最左列是IS NULL,后续列全失效
唯一索引允许多个NULL,是因为它主动跳过NULL比较
这不是索引“坏了”,是标准行为:UNIQUE约束检查时,只要任一列值为NULL,整行就绕过该列的判重逻辑。所以(1, NULL)插两次不报错,(NULL, 1)和(NULL, 1)也不冲突。
这也导致两个后果:
-
WHERE col IS NULL在唯一索引上几乎无法利用索引加速——因为优化器知道这里可能有多个匹配,且它们不在有序路径上 - 联合唯一索引如
(a, b),只要a IS NULL,哪怕b值完全不同,这组组合也“隐身”于唯一性校验之外
想让IS NULL查询稳定走索引,得绕开NULL语义本身
硬改MySQL对NULL的处理是不可能的。可行路径只有从数据建模层面隔离问题:
- 建表时加
NOT NULL约束,用哨兵值替代,比如status TINYINT NOT NULL DEFAULT -1 - 加生成列:如原始字段
mobile VARCHAR(20) NULL,再建mobile_key VARCHAR(20) STORED AS (COALESCE(mobile, '__NULL__')),并在其上建普通索引 - 业务层统一转换:ORM写入前把
null转成'__MISSING__',查询时也对应改条件
真正容易被忽略的是:即使你给列建了索引,只要它允许NULL,且业务中高频查IS NULL,你就得默认接受优化器随时可能放弃索引——这不是配置问题,是B+树结构与NULL语义的根本冲突。


















