MySQL中NULL值在MyISAM和InnoDB引擎中处理方式不同:MyISAM不节省空间但索引支持多NULL,InnoDB节省存储却降低索引效率,跨引擎迁移易引发查询性能与逻辑错误。

MyISAM中NULL值不节省空间,但索引可存多个NULL
MyISAM对NULL的处理是“形式上支持,实质上不优化”:它仍为每个可为NULL的列分配一个NULL位图(bitmask),但这个位图无论实际值是否为NULL都会存在,因此不会因NULL增多而减少磁盘占用。你看到的“NULL不占存储空间”说法,在MyISAM里基本不成立。
不过MyISAM的B-tree索引允许同一列中存在多个NULL值,且IS NULL查询能走索引——因为MyISAM把NULL统一视为一个特殊标记,放在索引树最左端(升序)或最右端(降序),定位成本低。
- 建表时若未加
NOT NULL约束,MyISAM默认允许NULL,但不会自动压缩 -
SELECT COUNT(*)能秒出结果,但SELECT COUNT(col)会跳过NULL,和语义一致 - 全文索引(FULLTEXT)不索引
NULL值,也不索引空字符串'',这点需注意
InnoDB中NULL节省行存储空间,但影响索引效率
InnoDB真正实现了NULL的空间优化:每行记录头部有一个NULL位图,只用1个字节就能标记最多8个可为NULL列的状态;如果某列为NULL,它既不存数据、也不记长度,比存''或' '更省空间。但代价是——索引结构更复杂。
InnoDB的B+树索引把NULL当作最小值(MIN_VALUE)处理,所有NULL被聚集在索引最左叶子页。当NULL占比高时,优化器很可能放弃走索引,直接全表扫描——因为它发现“查NULL”要遍历大量叶子页,还不如扫一遍聚簇索引。
- 联合索引如
(a, b),若a IS NULL AND b = 'x',几乎无法利用该索引,因前导列a为NULL时,B+树无法确定子树范围 - 想加速
IS NULL查询?可建虚拟列:is_a_null TINYINT GENERATED ALWAYS AS (a IS NULL),再给它加索引 - InnoDB不缓存总行数,
SELECT COUNT(*) FROM t WHERE a IS NULL必须实际扫描,性能敏感场景需预估或冗余统计
NULL与空字符串在索引中的物理位置完全不同
很多人以为NULL和''在B+树里挨着,其实完全不是一回事。InnoDB中:NULL被强制映射到索引键的极小值(类似-∞),而''是一个合法的、可比较的字符串值,按字典序排在所有非空字符串之前,但明确大于NULL。
这意味着:WHERE col > ''不会命中NULL行,WHERE col >= ''也不会——因为NULL >= ''结果为UNKNOWN,被WHERE过滤掉。这种语义断裂极易引发漏查。
- 用
ORDER BY col ASC时,NULL总在最前,''紧随其后(若存在) - 唯一索引(UNIQUE)允许无限多个
NULL,但只允许一个''(除非你定义了col VARCHAR(10) UNIQUE NOT NULL) - 如果业务上“未填写”和“明确填了空”有区别,必须用不同值表达:比如用
NULL表示未提交,用''表示用户主动清空
跨引擎迁移时NULL行为差异最容易踩坑
从MyISAM转InnoDB(尤其MySQL 5.7→8.0升级)时,NULL相关逻辑可能悄然失效。典型表现:原来跑得飞快的IS NULL查询变慢、NOT IN子查询突然不返回任何结果、应用层判空逻辑报错。
根本原因在于两套引擎对NULL的“容忍度”不同:MyISAM索引结构简单,NULL就是个标记;InnoDB则严格遵循SQL标准三值逻辑,且索引下推(ICP)和MRR等优化机制对NULL判断更谨慎。
- 迁移前务必检查所有含
IS NULL的WHERE条件,用EXPLAIN确认是否走了索引 - 避免
NOT IN (SELECT x FROM t WHERE ...),改用NOT EXISTS,否则子查询一出NULL,整条语句结果就为NULL - 备份还原时,MyISAM表导出的
NULL在InnoDB中可能被误转成'',特别是用mysqldump --compatible=mysql40时
IS NULL条件,在MyISAM里可能是索引最优路径,在InnoDB里却触发全表扫描。设计阶段定死存储引擎,比后期调优重要十倍。


















