从零起步学习MySQL || 第六章:MySQL数据库中的一条数据是如何存储的?(结合源码深度解析)
一、InnoDB 的行格式(Row Format)
InnoDB 是 MySQL 默认的存储引擎。它在物理层面上存储表数据时,每一行(row)都按一定格式组织在 数据页(data page) 中。
1. InnoDB 支持的行格式
InnoDB 一共有四种行格式:
行格式 | 简介 |
---|---|
REDUNDANT | 最早的行格式(MySQL 5.0 之前),已不推荐使用。 |
COMPACT | MySQL 5.0 起默认格式(高效节省空间)。 |
DYNAMIC | MySQL 5.7 起常用,支持大字段页外存储。 |
COMPRESSED | 与 DYNAMIC 类似,但数据页还会压缩以节省空间。 |
👉 通常我们使用 COMPACT
或 DYNAMIC
。
配置方式示例:
CREATE TABLE t( id INT, name VARCHAR(255) ) ROW_FORMAT=COMPACT;
二、COMPACT 行格式的结构(重点)
每一条记录的存储结构如下(从前往后):
| 变长字段长度列表 | NULL标志位 | 记录头信息 | 实际数据(列数据) |
我们一部分一部分看:
1️⃣ 变长字段长度列表 (Variable-length field length list)
-
对于可变长类型(如
VARCHAR
,VARBINARY
,TEXT
),InnoDB 在行首存储这些字段的长度信息。 -
顺序是逆序存放(最后一个可变长列的长度在最前面)。
-
每个字段占 1 或 2 个字节:
-
若字段最大长度 ≤ 255,则用 1 字节;
-
否则用 2 字节。
-
举例:
CREATE TABLE user ( id INT, name VARCHAR(20), email VARCHAR(255) ) ROW_FORMAT=COMPACT;
则一条记录的行首会先存储:
email长度(2字节) | name长度(1字节)
2️⃣ NULL 标志位 (NULL bitmap)
-
若列允许为 NULL,InnoDB 会用位图存储每个字段是否为 NULL。
-
每 8 个列占用 1 个字节。
-
若列不允许为 NULL,则不出现在位图中。
例如有 10 个列,其中 6 个允许为 NULL,则 NULL 标志位占:
ceil(6 / 8) = 1 字节。
3️⃣ 记录头信息(Record Header)
每条记录都有一个“头部信息”用于链表管理和事务控制,占 5字节:
字段 | 长度 | 作用 |
---|---|---|
delete_flag | 1 bit | 记录是否被删除(0=未删,1=已删) |
min_rec_flag | 1 bit | B+树叶子页中最小记录标志 |
n_owned | 4 bit | 当前记录拥有的记录数(分组管理) |
heap_no | 13 bit | 记录在页中的位置编号 |
record_type | 3 bit | 记录类型(普通/最小/最大等) |
next_record | 16 bit | 下一条记录的偏移量 |
4️⃣ 实际列数据(User Data)
这一部分是真正存放你的列数据的地方。
-
固定长度列(INT、CHAR等)直接按顺序存。
-
可变长度列的长度由前面的“变长字段长度列表”指定。
-
NULL 值不会真正存储数据,只在 NULL 标志位体现。
三、记录的额外信息与真实数据
总结一下:
类型 | 含义 | 作用 |
---|---|---|
额外信息 | 变长字段长度列表、NULL 标志位、记录头信息 | 用于描述数据、链表管理、页组织 |
真实数据 | 用户定义的列值(包括主键、普通列等) | 实际的业务数据 |
在 InnoDB 页中,一行记录不仅包含你的列数据,还附带了这些额外的元数据,这使得 InnoDB 能实现事务、索引、MVCC 等高级特性。
四、VARCHAR(n) 中的 n 最大取值是多少?
这个问题很多人容易搞混。
-
VARCHAR(n)
中的n
表示字符数,而不是字节数。 -
对于 utf8mb4 编码,一个字符最多占 4 字节。
-
单行最大字节限制为 65535 字节。
因此:
一行所有列(包括额外信息)加起来不能超过 65535 字节。
粗略估算:
-
若使用
utf8mb4
,VARCHAR(n)
最大大约是65535 / 4 = 16383
字符。 -
实际可取更少,因为还有行头和其他列占空间。
五、行溢出(Row Overflow)与 MySQL 的处理机制
1️⃣ 概念
当行太大(如 TEXT、BLOB、或 VARCHAR 非常长),无法全部存进数据页(一个页大小通常 16KB = 16384 字节)时,就会发生行溢出(Row Overflow)。
2️⃣ 处理机制
取决于行格式:
行格式 | 溢出存储方式 |
---|---|
COMPACT | 把大字段的前 768 字节存在页内,其余部分存到溢出页(overflow page),页内只保存一个 20 字节的指针指向溢出页。 |
DYNAMIC | 整个大字段都存放在溢出页中,页内只保存 20 字节指针。 |
COMPRESSED | 类似 DYNAMIC,但数据页和溢出页会压缩存储。 |
3️⃣ 读取流程
读取溢出字段时,InnoDB:
-
先在主记录中找到溢出页指针;
-
再去溢出页中加载完整数据。
六、总结图示(COMPACT 行格式)
+--------------------------------------------------------------+ | 变长字段长度列表 | NULL标志位 | 记录头信息 | 实际列数据 | | (逆序存放) | (是否为NULL) | (管理信息) | (用户真实数据) | +--------------------------------------------------------------+
示意:
| name_len | email_len | null_bits | header | id | name | email |
✅ 总结要点复习
内容 | 关键点 |
---|---|
行格式 | REDUNDANT / COMPACT / DYNAMIC / COMPRESSED |
COMPACT结构 | [变长字段长度列表][NULL标志位][记录头][数据] |
记录额外信息 | 长度列表、NULL位图、头信息(5字节) |
VARCHAR(n)最大 | n 最大约 16383(utf8mb4时),受65535字节限制 |
行溢出 | 超出页大小时,大字段移至溢出页,页内保存指针 |
不同行格式溢出差异 | COMPACT 存前768字节,DYNAMIC 全部移出页内 |