在早期的 InnoDB 版本中,由于文件格式只有一种,因此不需要为此文件格式命名。随着 InnoDB 引擎的发展,开发出了不兼容早期版本的新文件格式,用于支持新的功能。为了在升级和降级情况下帮助管理系统的兼容性,以及运行不同的 MySQL 版本,InnoDB 开始使用命名的文件格式。
- Antelope: 先前未命名的,原始的 InnoDB 文件格式。它支持两种行格式:COMPACT 和 REDUNDANT。6 的默认文件格式。可以与早期的版本保持最大的兼容性。不支持 Barracuda 文件格式。
- Barracuda: 新的文件格式。它支持 InnoDB 的所有行格式,包括新的行格式:COMPRESSED 和 DYNAMIC。与这两个新的行格式相关的功能包括:InnoDB 表的压缩,长列数据的页外存储和索引建前缀最大长度为 3072 字节。
ROW_FORMAT 值:
ROW_FORMAT | 支持索引前缀 | 独立表空间压缩 | 系统表空间压缩 |
COMPRESSED | 3072 字节 | 支持 | 不支持 |
DYNAMIC | 3072 字节 | 不支持 | 不支持 |
COMPACT | 768 字节 | 不支持 | 支持 |
REDUNDANT | 768 字节 | 不支持 | 支持 |
在 mysql 5.7.9 及以后版本,默认行格式由 innodb_default_row_format 变量决定,它的默认值是 DYNAMIC,也可以在 create table 的时候指定 ROW_FORMAT=DYNAMIC。用户可以通过命令 SHOW TABLE STATUS LIKE ‘table_name’ 来查看当前表使用的行格式,其中 row_format 列表示当前所使用的行记录结构类型。
如果要修改现有表的行模式为 compressed 或 dynamic,必须先将文件格式设置成 Barracuda:set global innodb_file_format=Barracuda;,再用 ALTER TABLE tablename ROW_FORMAT=COMPRESSED; 去修改才能生效。
InnoDB 行存储
InnoDB 存储引擎和大多数数据库一样(如 Oracle 和 Microsoft SQL Server 数据库),记录是以行的形式存储的。这意味着页中保存着表中一行行的数据。这些页以树形结构组织,这颗树称为 B 树索引。表中数据和辅助索引都是使用 B 树结构。维护表中所有数据的这颗 B 树索引称为聚簇索引,通过主键来组织的。聚簇索引的叶子节点包含行中所有字段的值,辅助索引的叶子节点包含索引列和主键列。
行记录最大长度
- 页大小(page size)为 4KB、8KB、16KB 和 32KB 时,行记录最大长度(maximum row length)应该略小于页大小的一半
- 默认页大小为 16KB,行记录最大长度应该略小于 8KB,因此一个 B+Tree 叶子节点最少有 2 个行记录
- 页大小为 64KB 时,行记录最大长度略小于 16KB
CHAR(N)与 VARCHAR(N)
N 指的是字符长度,而不是 Byte 大小,在不同的编码下,同样的字符会占用不同的空间,如 LATIN1(定长编码)和 UTF8(变长编码)
变长列
在 InnoDB 中,变长列(variable-length column)可能是以下几种情况
- 长度不固定的数据类型,例如 VARCHAR、VARBINARY、BLOB、TEXT 等
- 对于长度固定的数据类型,如 CHAR,如果实际存储占用的空间大于 768 Byte,InnoDB 会将其视为变长列
- 变长编码下的 CHAR
行溢出
当行记录的长度没有超过行记录最大长度时,所有数据都会存储在当前页
当行记录的长度超过行记录最大长度时,变长列(variable-length column)会选择外部溢出页(overflow page,一般是 Uncompressed BLOB Page)进行存储
- Compact+Redundant:保留前 768 Byte 在当前页(B+Tree 叶子节点),其余数据存放在溢出页。768 Byte 后面跟着 20 Byte 的数据,用来存储指向溢出页的指针
- Dynamic+Compressed:仅存储 20 Byte 数据,存储指向溢出页的指针,这时比 Compact 和 Redundant 更高效,因为一个 B+Tree 叶子节点能存放更多的行记录
Redundant
MySQL 5.0 之前的 ROW_FORMAT格式
字段偏移列表 | 记录头信息 | ROWID | TransactionID | RollPointer | 列 1 | … | 列 n |
字段偏移列表
- 按照列的顺序逆序放置
- 列长度小于 255 Byte,用 1 Byte 存储
- 列长度大于 255 Byte,用 2 Byte 存储
记录头信息
名称 | 大小(bit) | 描述 |
() | 1 | 未知 |
() | 1 | 未知 |
deleted_flag | 1 | 该行是否已被删除 |
min_rec_flag | 1 | 如果该行记录是预定义为最小的记录,为 1 |
n_owned | 4 | 该记录拥有的记录数,用于 Slot |
heap_no | 13 | 索引堆中该条记录的索引号 |
n_fields | 10 | 记录中列的数量,一行最多支持 1023 列 |
1byte_offs_flag | 1 | 偏移列表的单位为 1 Byte 还是 2 Byte |
next_record | 16 | 页中下一条记录的相对位置 |
Total | 48 (6Byte) | nothing |
隐藏列
- ROWID:没有显式定义主键或唯一非NULL的索引时,InnoDB会自动创建6Byte的ROWID
- TransactionID:事务ID
- RollPointer:回滚指针列
Compact
MySQL 5.0引入,MySQL 5.1默认ROW_FORMAT对比Redundant
- 减少了大约20%的空间
- 在某些操作下会增加CPU的占用
- 在典型的应用场景下,比Redundant快
格式
变长字段长度列表 | NULL标志位 | 记录头信息 | ROWID | TransactionID | RollPointer | 列1 | … | 列n |
变长字段长度列表
- 条件
- VARCHAR、BLOB等
- 变长编码(如UTF8)下的CHAR
- 放置排序:逆序
- 用2Byte存储的情况:需要用溢出页;最大长度超过255Byte;实际长度超过127Byte
NULL标志位
- 行记录中是否有NULL值,是一个位向量(Bit Vector)
- 可为NULL的列数量为N,则该标志位占用的CEILING(N/8)Byte
- 列为NULL时不占用实际空间
记录头信息
名称 | 大小(bit) | 描述 |
() | 1 | 未知 |
() | 1 | 未知 |
deleted_flag | 1 | 该行是否已被删除 |
min_rec_flag | 1 | 如果该行记录是预定义为最小的记录,为1 |
n_owned | 4 | 该记录拥有的记录数,用于Slot |
heap_no | 13 | 索引堆中该条记录的索引号 |
record_type | 3 | 记录类型,000(普通),001(B+Tree节点指针),010(Infimum),011(Supremum) |
next_record | 16 | 页中下一条记录的相对位置 |
Total | 40 (5Byte) | nothing |
Dynamic
MySQL版本是5.7,它的默认⾏格式就是Dynamic。
dynamic行格式,列存储是否放到off-page页,主要取决于行大小,它会把行中最长的那一列放到off-page,直到数据页能存放下两行。TEXT/BLOB列<=40bytes时总是存放于数据页。这种方式可以避免compact那样把太多的大列值放到B-tree Node,因为dynamic格式认为,只要大列值有部分数据放在off-page,那把整个值放入都放入off-page更有效。
Compressed
compressed物理结构上与dynamic类似,但是对表的数据行使用zlib算法进行了压缩存储。在longblob列类型比较多的情况下用,可以降低off-page的使用,减少存储空间(一般40%左右),但要求更高的CPU,buffer pool里面可能会同时存储数据的压缩版和非压缩版,所以也多占用部分内存。