MySQL中IS NULL查询常不走索引主因是NULL不参与B+树排序且优化器悲观估算成本;联合索引最左列为NULL时无法定位子树;UNIQUE索引上IS NULL更难优化;建议改NOT NULL或用覆盖索引。

MySQL对NULL值的查询不一定会让索引“失效”,但会显著干扰优化器是否选择索引、以及走索引后能过滤多少数据——根本原因在于NULL不参与B+树排序、不写入键值序列,且优化器对它的统计和成本估算天然悲观。
IS NULL 查询为什么经常显示 key=NULL
EXPLAIN中key为NULL只说明优化器这次没选索引,并非物理上不能走。真正卡住的是成本估算逻辑:当列允许NULL且实际占比高(比如超20%),优化器会认为WHERE col IS NULL走索引要大量回表,不如全表扫描快。验证方法很简单:
– 先查索引定义:SHOW INDEX FROM t WHERE Key_name = 'idx_col';,确认Null列值为YES
– 再强制走索引:EXPLAIN SELECT * FROM t FORCE INDEX (idx_col) WHERE col IS NULL;,对比rows是否明显下降
– 如果强制后rows大幅减少,说明索引本身可用,只是被低估了;此时运行ANALYZE TABLE t;更新统计信息,有时就能让优化器自动改选
联合索引里最左列是 NULL 就断掉匹配
InnoDB联合索引依赖最左前缀连续提供可定位的非空值。一旦最左列是IS NULL,B+树就找不到起始页位置,后续列的有序性彻底作废。
– WHERE a IS NULL AND b = 1 → 基本不走(a,b)索引
– WHERE a = 1 AND b IS NULL → 可走,a = 1先定位子树,再在该子树内线性检查b IS NULL
– WHERE a IS NULL OR b = 1 → 几乎必然退化为全表扫描,OR加IS NULL是双重否定
这不是bug,是B+树设计决定的:索引本质依赖有序性,而NULL天然无序
UNIQUE索引上 IS NULL 查询特别难优化
UNIQUE约束允许无限多个NULL(SQL标准行为),这和“唯一性”语义冲突——优化器无法假设IS NULL结果集很小,只能按“可能有成百上千个”的悲观估计做成本计算。
– 即使你只有一行NULL,优化器仍倾向全表扫描
– WHERE col IS NULL在UNIQUE(col)上大概率回退到type=ALL
实操建议:
– 业务上真需要频繁查IS NULL,优先考虑改字段为NOT NULL DEFAULT 'N/A'或0,彻底规避问题
– 若必须保留NULL且查询高频,可用覆盖索引绕过:比如常查id和col,就建INDEX(col, id),减少回表开销
innodb_stats_method 默认值拉低基数估算
innodb_stats_method默认值nulls_equal把所有NULL当作同一个值,直接拉低索引列基数估算。
– 比如status列有100万行,其中95万是NULL,剩下5万均匀分布在A/B/C,优化器按nulls_equal算:总共就4个“不同值”,判定该索引低效 → 跳过索引选全表扫描
– nulls_unequal和nulls_ignored已在MySQL 5.7.22+禁用,你只能接受nulls_equal的现实
补救手段只有:ANALYZE TABLE t;、强制索引、或改用NOT NULL DEFAULT设计
真正容易被忽略的点是:NULL不是“值”,而是“缺失标记”,它不进B+树键值序列、不参与比较、不贡献区分度。所以别指望靠加索引解决IS NULL慢的问题,优先从字段设计和查询重写入手。


















