云计算百科
云计算领域专业知识百科平台

InnoDB 行格式详解:DYNAMIC / COMPACT / COMPRESSED / REDUNDANT

一、行格式总览

1.1 四种行格式与默认值

行格式出现时代是否默认总体定位
REDUNDANT InnoDB 最早(MySQL 4.1 时代) ❌ 历史遗留,仅兼容旧表
COMPACT MySQL 5.0 引入 5.6 默认 5.x 时代的主流格式
DYNAMIC MySQL 5.7 引入 5.7 / 8.0 默认 当前默认,长字段全量溢出
COMPRESSED MySQL 5.7 引入 ❌ DYNAMIC + 页级压缩,时间换空间

加粗要点:8.0 默认行格式是 DYNAMIC,由系统变量 innodb_default_row_format 控制(默认值 DYNAMIC)。

1.2 查看与指定行格式

— 查看某张表的行格式
SHOW TABLE STATUS LIKE 't1'\\G — 看 Row_format 字段
SHOW CREATE TABLE t1\\G — 看 ROW_FORMAT 子句

— 建表 / 改表时指定行格式
CREATE TABLE t1 (id INT PRIMARY KEY, name VARCHAR(100)) ROW_FORMAT=DYNAMIC;
ALTER TABLE t1 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;

1.3 本系列分析的基线

上篇InnoDB 页结构详解:页类型、页头页尾、数据行与页目录-CSDN博客 分析的"额外信息 + 真实数据"结构,就是 DYNAMIC 行格式。本篇以它为主线,再看另外三种的差异。


二、DYNAMIC 行格式(默认)

2.1 总体结构

COMPACT / DYNAMIC 行格式(一条记录的物理布局)

记录起点
│
▼
┌──────────────────┬─────────────┬──────────────────┬─────────────────────────────┐
│ 变长字段长度列表 │ NULL 值位图 │ 记录头(5B/40bit) │ 真实数据(列值 + 隐藏列) │
│ (按列逆序,2B/列)│ (1bit/列) │ heap_no/next_record │ DB_TRX_ID(6) DB_ROLL_PTR(7)…│
└──────────────────┴─────────────┴──────────────────┴─────────────────────────────┘
│
└── next_record ──▶ 下一条记录的"真实数据起点"

部分组成大小
额外信息 变长字段长度列表 + NULL 值列表 + 记录头 长度列表/NULL 位图变长,记录头固定 5B
真实数据 各用户列的值(含主键列) + 隐藏列 变长

2.2 真实数据里到底有什么

主键   DB_TRX_ID    DB_ROLL_PTR    其余列

当表中存在主键时直接将对应的主键放在前面;如果不存在主键,但是存在非空唯一键,将第一个非空唯一键作为主键组织数据;如果两者都没有,则自动生成主键。

对于二级索引树,放在第一个位置的是对应的索引,数据是主键 (InnoDB下)


三、NULL 值列表的工作原理

3.1 三条规则

  • 每个允许为 NULL 的列占 1 bit,不允许为 NULL 的列不占位;
  • 位图逆序存放(最后一列对应最低位);
  • 若所有列都是 NOT NULL,整个 NULL 列表不占字节。
  • 示例:表 t(id INT NOT NULL, name VARCHAR(10) NULL, age INT NULL, addr VARCHAR(20) NULL)
    → 可空列按定义顺序:name、age、addr(3 列 → 位图占 3 bit,不足 1 字节按 1 字节对齐?)
    位图(逆序):addr(bit2) age(bit1) name(bit0)
    若 name=NULL, age=20, addr='NJ' → 位图 = 001(仅 bit0=1)

    NULL 位图按字节存放,实际占用的字节数 = 向上取整(可空列数 / 8)。

    3.2 为什么"自右向左"?

    • 记录头是"额外信息"与"真实数据"的分界线,next_record 指向交界处;
    • 设计目标:向右直接读数据(按列顺序),向左读额外信息;
    • 变长长度列表、NULL 位图都逆序存放,正是为了配合"向左解析"的方向。

    3.3 NULL 位图里的 bit,怎么知道对应哪一列?

    靠表结构元数据,不需要在行内额外存列号:

  • 表结构(列定义顺序、哪些列可空、哪些是变长)保存在数据字典中,用户表的 .ibd 里还有一份 SDI 页作为表结构的 JSON 冗余副本;
  • 解析一行时,从数据字典拿到"可空列清单(按定义顺序)";
  • 位图按逆序与可空列一一对应:第 k 个可空列对应位图倒数第 k 位(从右往左数);
  • 该 bit 为 1 → 该列为 NULL,真实数据中不占空间;为 0 → 正常取值。
  • 好处:既是空间节省(NULL 不占盘),也是语义精确(用位图区分"NULL"与值为"NULL",不会混淆)。


    四、变长字段长度列表

    4.1 哪些列算"变长字段"

    类型是否变长
    VARCHAR / VARBINARY / TEXT / BLOB ✅ 必为变长
    CHAR(N)(单字节字符集,如 latin1) ❌ 定长(按 N 字节)
    CHAR(N)(多字节字符集,如 utf8mb4) ⚠️ 当最大字节长度可能超过 255 字节时按变长处理(utf8mb4 下即 N>63);否则仍定长

    。

    4.2 1~2 字节长度规则

    变长字段长度列表逆序存放:第一个变长列的长度在列表最右端。每个字段长度占 1 或 2 字节,判定规则:

  • 该列最大长度 ≤ 255 字节 → 固定用 1 字节记录(无需标志);
  • 该列可能超过 255 字节 → 用 2 字节记录,且第一个字节的最高位(MSB)置 1 作为标志位。
  • ( InnoDB规定按照磁盘按照大端存储,但是单个字节内无论大小端都是 MSB –> LSB,作者犯蠢了 )

    存储示例:某变长列实际长度 = 257 字节(0x0101,>255)
    → 2 字节表示:0000 0001 1000 0001

    读取:先读第一字节(自右向左)1000 0001 ,发现最高位是 1 → 这是 2 字节表示(InnoDB规定存储方式为大端)
    → 长度 = (000 0001) << 8 | 0000 0001 = 0x101 = 257

    长度 = (第一字节的低 7 位) << 8 | 第二字节的 8 位

    4.3 问:2 字节能到 2¹⁵-1 ,大于 16KB (一个页的大小),一个页会不会连一行也放不下?

    InnoDB 明确规定:一个页至少要能放下两行记录(否则 B+Tree 每页只有 1 条记录,树就退化成链表)。由此推出行大小约束:

    16KB 页 → 行最大 ≈ 半页 ≈ 8KB(实际约 8126 字节,还要扣行开销)

    对于超长字段,dynamic 的溢出处理是将数据放在溢出页中,对应字段只存放溢出页的 20 字节地址

    DYNAMIC 行格式:超长字段(如 TEXT/BLOB/LONGTEXT)
    ┌─────────────────────────┐
    │ 行内:20 字节指针 │ ──▶ 指向溢出页
    │ (该列完整数据不留在行内) │
    └─────────────────────────┘

    溢出页 = BLOB 页(FIL_PAGE_TYPE_BLOB),数据过大时可由多个溢出页链式存放

    • 特点:整列数据全部放入溢出页,行内只保留 20 字节的指针(指向溢出页位置);
    • 这样行大小始终可控,不会因为一个大字段撑破"半页"约束;
    • 溢出与否的本质判断:该列实际长度是否超过行内可容纳的阈值;溢出后行内表现为指针,变长长度列表记录的是行内实际保留部分的长度。

    五、COMPACT 行格式:

    5.1 与 DYNAMIC 的唯一区别

    COMPACT 与 DYNAMIC 的额外信息、NULL 列表、记录头完全一致,唯一的差别在超长字段的处理:

    行格式超长字段行内保留溢出部分行内额外开销
    COMPACT 前 768 字节 其余放入溢出页 20 字节指针
    DYNAMIC 0 字节(全溢出) 全部放入溢出页 20 字节指针

    COMPACT:行内 = [前 768 字节数据][20 字节溢出页指针]
    即"768 + 20",剩余部分在溢出页

    5.2 768 字节前缀的价值

    • 保留了长字段的前 768 字节在行内,可支持前缀索引/前缀匹配(不必每次都进溢出页);
    • 对"小字段混合大字段"的表,某些查询可以只读行内前缀就返回。

    5.3 何时选 COMPACT

    • 需要兼容老版本行为;
    • 业务上对长文本有前缀查询需求;
    • 默认建议仍用 DYNAMIC(官方默认),除非有明确理由。

    六、COMPRESSED 行格式:DYNAMIC + 压缩

    6.1 压缩原理

    • 行结构与 DYNAMIC 完全相同(包括溢出处理方式);
    • 额外做的事:对数据(含页)进行 zlib 压缩,减少磁盘空间占用;
    • 通过 ROW_FORMAT=COMPRESSED + KEY_BLOCK_SIZE=N 启用(N 为压缩页大小)。

    6.2 时间换空间

    收益代价
    磁盘占用显著下降(压缩率取决于数据重复度) CPU 压缩/解压开销
    内存中压缩页可保持压缩状态,间接省内存 随机访问变慢(每次读要解压)

    6.3 注意点

    • 适合空间紧张、CPU 富余、以顺序扫描为主的场景(如归档表);
    • 不适合写放大严重、CPU 已是瓶颈的 OLTP 核心表;
    • 压缩页类型为 FIL_PAGE_COMPRESSED,与 8.0 的"表空间页压缩(COMPRESSION='zlib')"是不同机制,不要混淆。

    七、REDUNDANT 行格式:历史遗留

    • InnoDB 最早的格式(MySQL 4.1 时代),只为兼容旧表而存在,新表不应使用;
    • 读取效率、空间利用率都不如 COMPACT 系列,官方不建议新表使用。
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » InnoDB 行格式详解:DYNAMIC / COMPACT / COMPRESSED / REDUNDANT
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!