这是《MySQL 8.0 源码与内核实战》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 记录格式决定一行数据在页内如何编码,影响 NULL 处理、变长列、主键生成、页利用率和 SQL 层字段转换。
15.1 行格式
| 格式 | 说明 |
|---|---|
| REDUNDANT | 老格式 |
| COMPACT | 8.0 常见默认 |
| DYNAMIC | 长字段更倾向溢出页 |
| COMPRESSED | 支持压缩 |
查看:
SELECT NAME, ROW_FORMAT
FROM information_schema.innodb_tables
WHERE NAME LIKE 'db/%';
15.2 COMPACT 记录头
variable length list
NULL bitmap
record header
column values
记录头包含:
- delete mark;
- min rec 标记;
- owned 记录数;
- heap number;
- record type;
- next record offset;
- info flags。
页内记录通过 next offset 串成有序链。
15.3 NULL
NULL 存在于 bitmap 中,不为 NULL 值本身分配列空间,但列定义仍影响元数据和上限。
CREATE TABLE t(
id INT PRIMARY KEY,
a VARCHAR(100),
b INT
) ENGINE=InnoDB;
影响:
- 索引仍可包含 NULL;
- NULL 参与比较遵循 SQL 三值逻辑;
- 聚合函数处理不同;
- 唯一索引允许多个 NULL,除非列声明 NOT NULL;
- NULL 排序顺序与排序方向相关。
15.4 变长列
VARCHAR 长度列表记录实际字节数。字符集决定最大字节:
CREATE TABLE t(
id BIGINT PRIMARY KEY,
name VARCHAR(100)
) DEFAULT CHARSET=utf8mb4;
utf8mb4 每字符最多 4 字节,因此 VARCHAR(100) 最大可到 400 字节。
15.5 隐式主键
若表没有主键也没有非 NULL 唯一索引:
InnoDB generates hidden row id
问题:
- 隐藏 ID 全局递增,写入竞争集中;
- 无法作为业务定位;
- 复制和增量工具处理不友好。
生产建议显式定义主键。
15.6 大字段
DYNAMIC 行格式倾向于只保留前缀在 B+Tree 内,其余放溢出页。
| 影响 | 说明 |
|---|---|
| 页占用 | 大字段可能溢出 |
| IO | 读取完整值更多页 |
| 缓存 | 命中率下降 |
| 网络 | 返回大字段变慢 |
| 排序 | 临时表成本增加 |
大字段业务建议分表或放对象存储,数据库只存引用。
15.7 记录解析
InnoDB 内部记录与 MySQL Field 之间需要转换:
InnoDB record bytes
-> row mysql template
-> MySQL Field / Item
-> result set
相关源码:
| 文件 | 职责 |
|---|---|
rem0rec.cc |
记录头和格式 |
row0mysql.cc |
记录转换 |
data0data.cc |
数据类型 |
field.cc |
Server 字段 |
15.8 页利用率
行越宽,一页容纳的行越少,范围扫描读取相同行数需要更多页。
优化:
- 使用合适类型;
- 避免超长 VARCHAR;
- 大字段分离;
- 减少冗余列;
- 控制主键长度;
- 避免无效索引。
本章小结
记录格式决定行在页内的物理表达。COMPACT 和 DYNAMIC 是重点,理解记录头、NULL 位图、变长列表、溢出页和隐藏主键,有助于解释页碎片、行宽、回表和大字段性能。
思考题
- NULL 为什么可能节省页内空间但仍有代价?
- 字符集如何影响 VARCHAR 最大字节?
- 为什么建议显式主键?
- DYNAMIC 与 COMPACT 对长字段处理有什么差异?
- 行宽如何影响范围扫描?