InnoDB 行格式(row_format)
InnoDB 行格式类型
InnoDB 表中的一行行数据被称为记录,这些记录在磁盘上的存储形式被称为行格式(row_format)。
从 MySQL 5.7 开始,InnoDB 有 4 种行格式:
1. REDUNDANT:最古老的行格式,比较浪费空间。
2. COMPACT:MySQL 5.1 默认的行格式,是一种紧凑型的行格式,相比 REDUNDANT 更省空间。
3. DYNAMIC:MySQL 5.7 默认的行格式,跟 COMPACT 很类似,区别在于 DYNAMIC 对于溢出列的处理不同,当存在溢出列时,DYNAMIC 在溢出列的真实数据处只存储 20 字节数据(这 20 字节中的 16 字节存储指向溢出页的地址,4 字节存储该字段的真实数据占用的字节数),真实数据都存放在溢出页(Off Page)中,而不像 COMPACT 还会在数据页中存放真实数据的前 768 个字节。
4. COMPRESSED:存储的数据会用压缩算法进行压缩,对于 BLOB、TEXT 等大长度类型的数据可以有效节省空间。
为了避免 B+ 树退化成链表,MySQL 规定每个数据页至少存放 2 条记录,因此当判断无法存放 2 条记录时(InnoDB 数据页默认大小为 16KB,每条记录最大支持 65535 字节,因此一条记录足够大时,1 个数据页甚至连 1 条记录都无法存储),会使用溢出页降低记录在数据页中的空间占用,从而保证每个数据页至少存放 2 条记录。
由于 DYNAMIC 是 MySQL 5.7 开始默认的行格式,因此有必要重点掌握 DYNAMIC 行格式的细节。下面是 DYNAMIC 行格式的示意图:
一、记录的额外信息
记录的额外信息是 InnoDB 为了管理记录添加的一些辅助信息,分为 3 个部分:
1. 变长字段长度列表
2. NULL值列表
3. 记录头信息
变长字段长度列表
MySQL 支持一些变长的字段类型,如 VARCHAR、TEXT、BLOB 等,这些字段实际存储多少字节是不确定的,因此需要存储这些变长字段实际占用的字节数,变长字段长度列表就是用来存储这个的。
注意:在使用变长编码的字符集(即不同字符占用的字节数可能不同)时,CHAR(M) 字段占用的字节数无法确定,因此 CHAR(M) 字段也会被视作变长字段。此外,为了减少 CHAR(M) 字段更新产生的空间碎片,MySQL 规定使用变长编码字符集的 CHAR(M) 字段至少占用 M 字节。
InnoDB 会根据一定的规则,决定使用 1 个字节还是 2 个字节来存储变长字段真实数据占用的字节数:
在 DYNAMIC 行格式中,如果某个变长字段使用了溢出页,那么在变长字段长度列表中的记录的这个字段占用 20 字节。
在 DYNAMIC 行格式中,所有变长字段的真实数据占用的字节数都存放在记录的最前面,它们按照变长字段在表中顺序的相反顺序存储。
NULL 值列表
MySQL 允许字段为 NULL,为了减少存储 NULL 值浪费的空间,引入了 NULL 值列表。
在 NULL 值列表中,用 1 个 bit 位表示一个允许存储 NULL 值的字段是否为 NULL(1 表示 NULL,0 表示非 NULL),按照允许存储 NULL 值的字段在表中顺序的相反顺序存储。
如果允许存储 NULL 值的字段大于 8 个,NULL 值列表会使用 2 个字节来存储,以此类推。
如果表中没有允许存储 NULL 值的字段,NULL 值列表就不存在。
记录头信息
记录头信息存储描述记录的一些元数据,在 DYNAMIC 行格式中固定使用 5 个字节(即 40 位)。
二、记录的真实数据
记录的真实数据除了包含我们自己定义的列的数据之外,还包含几个默认添加的隐藏列的数据。
隐藏列
在 InnoDB 表中,一共有 3 种隐藏列:
1. row_id:行 ID,唯一表示一条记录,占用 6 字节(row_id 不一定存在,当表没有自定义主键也没有不允许存储 NULL 的 UNIQUE 键时,才会默认添加row_id)
2. transaction_id:事务 ID,占用 6 字节
3. roll_pointer:回滚指针,占用 7 字节
为了可读性,上面这几个隐藏列是被重命名过的。实际在 InnoDB 表中,这几个隐藏列分别为:DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR。