题目
MySQL 是怎么存储 NULL 的?
答案
MySQL 怎么存储,其实问的是 InnoDB 怎么存储,如果你清楚 **InnoDB 存储引擎一行记录的格式**,那么关于 NULL 值怎么存放这个问题,其实还是很简单的。 ## InnoDB 逻辑存储结构 InnoDB 中的所有数据都被逻辑地存放在一个空间中,这个空间被称之为 “**表空间**”(tablespace),表空间可以看作 InnoDB 存储引擎逻辑结构的最顶层,其中包含以下几个部分: - **段(segment)**:表空间是由各个段组成的,常见的段有: - 数据段(leaf node segment):InnoDB 是索引组织表(index organized)的,数据即索引,索引即数据,因此数据段其实就是索引(B+ 数的叶子结点) - 索引段(non-leaf node segment):B+ 树的非叶子节点 - 回滚段(rollback segment):也就是 undo log 中的数据 - **区(extent)**:一个段最多可以申请 4 个区,区由 64 个连续的页组成,**每个页大小为 16KB**,即每个区的大小为 64 * 16 = 1M - **页(page)**,也称为 “块”(block):**页是 InnoDB 磁盘管理的最小单位**。每个页中的数据组织形式是 “**行(row)**”,也就是说数据是按行进行存放的,每个页最多存放 16KB / 2 ~ 200 = 7992 行的记录 ![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/image-20230503101230287.png) ## InnoDB 行记录格式 InnoDB 存储引擎提供了 Compact 和 Redundant 两种格式来存放行记录格式,MySQL 5.1 默认保存为 Compact 格式。 可以通过 `show table status like 'table_name'` 来查看当前表使用的行记录格式。 Redundant 是为了兼容 MySQL 5.0 之前的版本来存在的,这里就不详细说了,主要看 Compact 格式,如下所示: ![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/image-20230516104409424.png) 首先,Compact 行格式的首部是一个非 NULL 变长字段(`varchar`)的长度列表,并且这个长度列表是按照列的顺序**逆序**放置的: - 当列的长度 < 255 字节,用 1 个字节标识 - 当列的长度 > 255 字节,用 2 个字节标识 举个例子,比如有四个字段 `(id, name, age, address)`,name 和 address 都是变长类型,有两行记录: | id | name | age | address | | ---- | ----- | ---- | ----------- | | 1 | Job | 20 | Los Angeles | | 2 | Julia | 21 | Panama | - name 列的长度分别是 3 和 5,十六进制表示 0x03 和 0x05 - address 列的长度分别是 11 和 6,十六进制表示 0xB 和 0x06 这两行记录对应的 Compact 格式如下图所示: ![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/image-20230516105429929.png) 第二个部分就是 NULL 标识位,用于指示这行数据中是否有 NULL 值,若有的话,则将对应的比特位置为 1。具体来说,每个列对应一个二进制位(bit),二进制位同样按照列的顺序**逆序**排列。 比如有一行记录 `id = 1, name = "admin", age = NULL`,那么这行记录对应的 NULL 标位就是 `100`(逆序排放,`[age, name, id]`),然后在高位补 0,最终就是 `0000 0100` ![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/image-20230503112448690.png) 第三部分是记录头信息(record header),固定占用 5 个字节(40 位),主要就是包含比如该行是否已被删除、页中下一条记录的相对位置等等之类的