深入解析:MySQL 中 NULL 值是否占用 1 bit 存储空间?

201 阅读4分钟

在 MySQL 的存储机制中,关于 NULL 值是否占用 1 bit 的存储空间,存在一个常见的理解误区。许多人认为“每个 NULL 值占用 1 bit”,但这并不完全准确。本文将通过 InnoDB 引擎的存储原理,详细解释 NULL 值的实际存储开销,并澄清这一误解。


一、核心结论

  1. 允许为 NULL 的列会引入位掩码(Bitmask) ,但 位掩码的开销是按字节分配,而非按单个 NULL 值分配。
  2. NULL 值本身不存储数据内容,但通过位掩码标记是否为 NULL
  3. 固定长度类型(如 INT)的 NULL 值不占用数据空间,仅通过位掩码标记。
  4. 可变长度类型(如 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. 固定长度类型(如 INTDATE

  • 非 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. 可变长度类型(如 VARCHARTEXT

  • 非 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 值数量无关。

六、设计建议

  1. 减少允许为 NULL 的列数:降低位掩码开销。
  2. 优先使用 NOT NULL:为字段赋予业务合理的默认值(如 0'')。
  3. 稀疏字段使用 NULL:对极少赋值的字段保留 NULL

七、总结

  • NULL 的存储成本 = 位掩码开销(按字节) + 数据空间(0 字节)。
  • 关键权衡:位掩码的隐性成本 vs 数据空间的显性节省。
  • 最终目标:通过合理设计,在存储效率、查询性能和数据语义清晰度之间取得平衡。