表的行格式(row format)决定了表中的行是如何物理存储的,修改它会影响到查询或者DML语句的性能。大部分的行会尽量将单行存储在单一的磁盘页中,这样能让查询或者索引查询运行更快,在buffer pool中更小的内存空间消耗和更少的IO请求。
表中的所有数据被拆分到多个page中,组成每个表的页数据放在排好序的B树索引中,表数据和二级索引都是采用这样的数据结构。
变长字段列(varchar, text,等类型)是数据存储在B树索引节点这个规则的一个例外,太长的变长字段无法放在B树的页上,这些列存储在单独分配的磁盘页面上,称为溢出页(overflow page),这样的列称为页外列(off-page)。页外列的值存储在溢出页的一个单链表中,每一个这样的列有它自己的一个或者多个溢出页面。依赖于列的长度,变长列的所有或者前缀值存储在B数中以避免浪费空间和一次单独的页读取。
InnoDB引擎支持四种行格式:REDUNDANT, COMPACT, DYNAMIC 和 COMPRESSED
Row Format | Compact Storage Characteristics | Enhanced Variable-Length Column Storage | Large Index Key Prefix Support | Compression Support | Supported Tablespace Types | Required File Format |
---|---|---|---|---|---|---|
REDUNDANT | No | No | No | No | system, file-per-table, general | Antelope or Barracuda |
COMPACT | Yes | No | No | No | system, file-per-table, general | Antelope or Barracuda |
DYNAMIC | Yes | Yes | Yes | No | system, file-per-table, general | Barracuda |
COMPRESSED | Yes | Yes | Yes | Yes | file-per-table, general | Barracuda |
下面分别介绍每种行格式
Redundant Row Format
这种格式提供了于旧版本的MySQL的兼容性,冗余行格式(redundant row format)被所有InnoDB 的文件格式(file format)支持。
使用冗余行格式的的表会存储变长字段的前768个字节的数据在B树节点中,剩余的数据存储在溢出页。固定长度的列但是长度大于或者等于768字节也会被编码成一个变长字段超过的字节存储在页外区域。比如,如果字符集的最大字节长度大于3, CHAR(255)的列可能会超过768字节的长度,就像utf8mb4一样。
如果列的值少于768字节就不会使用溢出页,由于列的值是存储在B树节点中可以省下一些IO。这对于较短的BLOB列很有效,单可能导致B树节点填充这样的数据而不是键值从而降低效率。包含很多BLOB列的表会导致B树节点太满并且包含的行太少,这使得整个索引的效率低于行更短或者列值存储在页外的情况。
冗余行格式的特点:
- 索引的每条记录包含一个6字节长度的头部。头部用来将连续的记录连接在一起,并用于行锁。
- 聚簇索引的记录包含用户定于的所有列。另外还有一个6字节的事务ID(DB_TRX_ID)和一个7字节长度的回滚段指针(Roll pointer)列。
- 如果没定于主键,每个聚簇索引行还包括一个6字节的行ID(row ID)字段。
- 每个二级索引记录包含所有定义的主键索引列。
- 一条记录包含一个指针来指向这条记录的每个列。如果一条记录的列的总长度小于128字节,这个指针是一个字节,否则为2个字节。这个指针数组称为记录目录(record directory)。指针指向的区域是这条记录的数据部分。
- 在内部,固定长度的字符字段比如 CHAR(10)通过固定长度的格式存储。尾部填充空格。
- 固定长度字段长度大于或者等于768字节将被编码成变长的字段,存储在页外区域。
- 一个SQL的NULL值存储一个字节或者两个字节在记录目录(record dirictoty)。对于变长字段null 值在数据区域存储0个字节。对于固定长度的字段,依然存储固定长度在数据部分。为null值保留固定长度空间允许列从null值更新为非空值而不会引起索引的分裂。
Compact Row Format
这种行格式比redundant 格式减少了大约20%的存储空间,作为代价,它会增加某些操作的CPU消耗。如果你的工作负载是受缓存命中率和磁盘速度限制,compact 行格式可能更快。如果你的工作负载受CPU速度限制,compact 行格式可能更慢。Compact 行格式被所有file format所支持。
跟redundant行格式一样,compact行格式存储变长字段的前768个字节在B树节点的索引记录中,剩余部分存储在溢出页。固定长度字段长度大于或者等于768字节的页按照变长字段进行编码存储在页外区域。
COMPACT 行格式的特点:
- 索引的每条记录包含一个5个字节的头部,头部前面可以有一个可变长度的头部。这个头部用来将相关连的记录链接在一起,也用于行锁。
- 记录头部的变长部分包含了一个表示null 值的位向量(bit vector)。如果索引中可以为null的字段数量为N,这个位向量包含 N/8 向上取整的字节数。比例如果有9-16个字段可以为NULL值,这个位向量使用两个字节。为NULL的列不占用空间,只占用这个位向量中的位。头部的变长部分还包含了变长字段的长度。每个长度占用一个或者2个字节,这取决了字段的最大长度。如果所有列都可以为null 并且制定了固定长度,记录头部就没有变长部分。
- 对每个不为NULL的变长字段,记录头包含了一个字节或者两个字节的字段长度。只有当字段存储在外部的溢出区域或者字段最大长度超过255字节并且实际长度超过127个字节的时候会使用2个字节的记录头部。对应外部存储的字段,两个字节的长度指明内部存储部分的长度加上指向外部存储部分的20个字节的指针。内部部分是768字节,因此这个长度值为 768+20. 20个字节的指针存储了这个字段的真实长度。
- 记录头部跟着非空字段的数据部分。
- 聚簇索引的记录包含了所以用户定于的字段。另外还有一个6字节的事务ID列和一个7字节的回滚段指针。
- 如果没有定于主键索引,则聚簇索引还包括一个6字节的Row ID列。
- 每个辅助索引记录包含为群集索引键定义的不在辅助索引中的所有主键列。如果任何一个主键列是可变长度的,那么每个辅助索引的记录头都有一个可变长度的部分来记录它们的长度,即使辅助索引是在固定长度的列上定义的
- 在内部,对于固定长度的字符集,固定长度的字段存储在固定长度的格式存储,比如 CHAR(10)
- 对于变长的字符集,比如 uft8mb3和utf8mb4, InnoDB试图用N字节来存储 CHAR(N)。如果CHAR(N)列的值的长度超过N字节,列后面的空格减少到最小值。CHAR(N)列值的最大长度是最大字符编码数 x N。比如utf8mb4字符集的最长编码为4,则列的最长字节数是 4*N.
DYNAMIC Row Format
动态行格式提供了于COMPACT行格式相同的特性,为长的变长列增强了存储功能,并支持大索引键前缀。Barracuda的文件格式支持动态行格式。
当表通过 ROW_FORMAT=DYNAMIC创建的时候,InnoDB存储长的变长列的值全部使用页外区域,聚簇索引中的记录只包含20个字节的指针指向溢出区域。固定长度的列长度大于或者等于768个字节时也按照变长字段编码。
字段是否存储在页外区域依赖了page size 和行的总长度。当一行数据太长,最长的字段会被选出来存储在页外区域,直到聚簇索引记录能适应B树的页。小于等于40字节的 text 和 BLOB类型的列会被存储在同一行。
动态行格式保持了在索引节点中存储整行(紧凑和冗余格式也是如此)的效率,但是动态行格式避免了用长列的大量数据字节填充b树节点的问题。动态行格式基于这样一种思想:如果长数据值的一部分存储在页外,那么通常最有效的方法是将整个值存储在页外。使用动态格式,较短的列可能保留在B-tree节点中,从而最小化给定行所需的溢出页面数量。
动态行格式支持前缀索引多达3072个字节。这个特性通过innodb_large_prefix 变量来控制,默认是启用。
COMPRESSED Row Format
压缩行格式提供了于动态行格式相同的特性,另外增加了对表和索引数据的压缩。Barracuda 文件格式支持这个压缩行格式。
压缩行格式使用与Dynamic行格式相似的页外存储