MySQL 8.0Notes

第 15 章:记录格式

zjc 于 2026-01-15 发布

这是《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

记录头包含:

  1. delete mark;
  2. min rec 标记;
  3. owned 记录数;
  4. heap number;
  5. record type;
  6. next record offset;
  7. info flags。

页内记录通过 next offset 串成有序链。

15.3 NULL

NULL 存在于 bitmap 中,不为 NULL 值本身分配列空间,但列定义仍影响元数据和上限。

CREATE TABLE t(
  id INT PRIMARY KEY,
  a VARCHAR(100),
  b INT
) ENGINE=InnoDB;

影响:

  1. 索引仍可包含 NULL;
  2. NULL 参与比较遵循 SQL 三值逻辑;
  3. 聚合函数处理不同;
  4. 唯一索引允许多个 NULL,除非列声明 NOT NULL;
  5. 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

问题:

  1. 隐藏 ID 全局递增,写入竞争集中;
  2. 无法作为业务定位;
  3. 复制和增量工具处理不友好。

生产建议显式定义主键。

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 页利用率

行越宽,一页容纳的行越少,范围扫描读取相同行数需要更多页。

优化:

  1. 使用合适类型;
  2. 避免超长 VARCHAR;
  3. 大字段分离;
  4. 减少冗余列;
  5. 控制主键长度;
  6. 避免无效索引。

本章小结

记录格式决定行在页内的物理表达。COMPACT 和 DYNAMIC 是重点,理解记录头、NULL 位图、变长列表、溢出页和隐藏主键,有助于解释页碎片、行宽、回表和大字段性能。

思考题

  1. NULL 为什么可能节省页内空间但仍有代价?
  2. 字符集如何影响 VARCHAR 最大字节?
  3. 为什么建议显式主键?
  4. DYNAMIC 与 COMPACT 对长字段处理有什么差异?
  5. 行宽如何影响范围扫描?