在 MySQL 的存储机制中,关于 NULL 值是否占用 1 bit 的存储空间,存在一个常见的理解误区。许多人认为“每个 NULL 值占用 1 bit”,但这并不完全准确。本文将通过 InnoDB 引擎的存储原理,详细解释 NULL 值的实际存储开销,并澄清这一误解。
一、核心结论
- 允许为
NULL的列会引入位掩码(Bitmask) ,但 位掩码的开销是按字节分配,而非按单个NULL值分配。 NULL值本身不存储数据内容,但通过位掩码标记是否为NULL。- 固定长度类型(如
INT)的NULL值不占用数据空间,仅通过位掩码标记。 - 可变长度类型(如
VARCHAR)的NULL值同样不占用数据空间,但比空字符串('')节省 1-2 字节的长度信息。
二、位掩码机制详解
1. 位掩码的作用
InnoDB 的每行数据开头有一个 NULL 位掩码,用于标记哪些允许为 NULL 的列实际存储了 NULL 值。
- 每个允许为
NULL的列在位掩码中对应 1 bit。 - 位掩码的总大小按字节向上取整:
位掩码字节数 = ⌈允许为 NULL 的列数 / 8⌉。
示例:
- 若表中有 5 个允许为
NULL的列,位掩码占用 1 字节(5/8=0.625 → 向上取整为1)。 - 若有 9 个允许为
NULL的列,位掩码占用 2 字节。
2. 位掩码的存储规则
列是否允许 NULL | 位掩码中是否占用 bit | 是否受实际值影响 |
|---|---|---|
允许为 NULL | 是(无论值是否为 NULL) | 否 |
不允许为 NULL | 否 | 否 |
- 关键点:
位掩码的大小仅由 允许为NULL的列数 决定,与实际存储的NULL值数量无关。即使某列的值始终非NULL,只要它允许为NULL,就会在位掩码中占用 1 bit。
三、NULL 值的实际存储开销
1. 固定长度类型(如 INT, DATE)
-
非
NULL值:占用固定长度空间(如INT为 4 字节)。 -
NULL值:- 数据部分:不占用任何空间。
- 位掩码:对应列在位掩码中标记为
1,占用 1 bit。
示例:
CREATE TABLE t1 (
id INT NULL, -- 允许为 NULL
age INT NOT NULL -- 不允许为 NULL
);
-
插入
id = NULL:- 数据部分:
age列占用 4 字节,id列不占空间。 - 位掩码:1 bit(标记
id为NULL),总位掩码为 1 字节(向上取整)。
- 数据部分:
2. 可变长度类型(如 VARCHAR, TEXT)
-
非
NULL值:- 存储数据内容 + 长度信息(1-2 字节)。
- 示例:
VARCHAR(255)的空字符串''占用 1 字节(长度信息为 0)。
-
NULL值:- 数据部分:不占用任何空间。
- 位掩码:对应列在位掩码中标记为
1,占用 1 bit。
对比示例:
CREATE TABLE t2 (
a VARCHAR(100) NULL, -- 允许为 NULL
b VARCHAR(100) NOT NULL DEFAULT '' -- 非 NULL
);
-
插入
a = NULL:- 数据部分:
a不占空间,b占用 1 字节(长度信息)。 - 位掩码:1 bit。
- 数据部分:
-
插入
a = '':- 数据部分:
a占用 1 字节(长度信息),b占用 1 字节。 - 位掩码:0 bit(因
a非NULL)。
- 数据部分:
四、综合存储开销分析
1. 位掩码的隐性成本
- 按字节分配:即使只有 1 个允许为
NULL的列,位掩码也占用 1 字节(而非 1 bit)。 - 设计建议:避免过度使用
NULL,减少允许为NULL的列数,以压缩位掩码大小。
2. 稀疏数据场景
- 适合使用
NULL:当某列的NULL值占比极高(如 90%),使用NULL可显著节省空间。 - 示例:用户表的
last_login_ip字段,90% 的用户未登录。
3. 密集数据场景
- 适合使用
NOT NULL:若某列大多数情况有值,使用NOT NULL+ 默认值更优。 - 示例:订单表的
amount字段,99% 的订单金额非空。
五、误区澄清
误区:“每个 NULL 值占用 1 bit”
-
错误原因:位掩码按字节分配,而非按
NULL值数量分配。 -
正确理解:
- 每个 允许为
NULL的列 在位掩码中占用 1 bit,无论其值是否为NULL。 - 位掩码的大小由允许为
NULL的列数决定,与实际的NULL值数量无关。
- 每个 允许为
六、设计建议
- 减少允许为
NULL的列数:降低位掩码开销。 - 优先使用
NOT NULL:为字段赋予业务合理的默认值(如0、'')。 - 稀疏字段使用
NULL:对极少赋值的字段保留NULL。
七、总结
NULL的存储成本 = 位掩码开销(按字节) + 数据空间(0 字节)。- 关键权衡:位掩码的隐性成本 vs 数据空间的显性节省。
- 最终目标:通过合理设计,在存储效率、查询性能和数据语义清晰度之间取得平衡。