前言

众所周知,Innodb 采用的是行式存储方式存储数据。

每个表中的数据被分为页面。组成每个表的页面被安排在一个称为b树索引的树数据结构中。

表数据和二级索引都使用这种结构。代表整个表的B-tree索引称为聚集索引,它是根据主键列组织的。聚集索引数据结构的节点包含行中所有列的值。二级索引结构的节点包含索引列和主键列的值。

但变长列(VARCHAR, VARBINARY, BLOB, TEXT)是列值存储在B-tree索引节点规则的一个例外。如果变长列太长,无法装入B-tree页面,则存储在单独分配的称为溢出页面的磁盘页面上。这样的列称为页外列。

页外列的值存储在溢出页的单链接列表中,每个这样的列都有自己的一个或多个溢流页列表。根据列的长度,所有或可变长度列值的前缀都存储在 b-树中,以避免浪费存储空间和不得不读取单独的页面。

类型

表的行格式决定了它的行是如何物理存储的,这反过来又会影响查询和DML操作的性能。不同的类型有着不同的特性。

在 MySQL 的迭代中,有两种文件格式类型:Antelope 和 Barracuda。

Antelope 是 MySQL5.6 的默认文件格式。可以与早期的版本保持最大的兼容性。不支持 Barracuda 文件格式。
Barracuda 是新版本支持的,支持InnoDB的所有行模式。

可通过 show variables like “innodb_file_format”; 查看当前支持的文件模式。

当前 Innodb 支持四种行模式:REDUNDANT、COMPACT、 DYNAMIC、 COMPRESSED。

行模式

是否紧凑型存储

变成类型列是否增强

长索引前缀支持

压缩支持

支持表空间类型

支持文件类型

REDUNDANT

N

N

N

N

system, file-per-table

Antelope or Barracuda

COMPACT

Y

N

N

N

system, file-per-table

Antelope or Barracuda

DYNAMIC

Y

Y

Y

N

file-per-table

Barracuda

COMPRESSED

Y

Y

Y

Y

file-per-table

Barracuda

特征

MySQL 中以页为基本单位来管理储存空间的,所有的记录都会被分配到页中,页默认为 16KB (16384 个字节)。

InnoDB存储引擎表是索引组织的,即B+树的结构。因此每个页中至少应该有两个行记录。因此如果当页中只能存放下一条记录,那么InnoDB存储引擎会自动将行数据存放到溢出页中。如果一页当中的记录过大,会截取前 768 个字节存入页中,其余的放入 BLOB Page。

以 varchar(255) 举例:在使用 UTF8 字符集存储下,每个字符用三个字节表示。故最多存储 255 * 3 = 765字节(大于或等于768字节的固定长度列被编码为可变长度列,可以存储在溢出页)。

varchar 共需要 3 部分储存空间:

  1. 真实数据
  2. 真实数据占用的字节长度
  3. NULL 值标识,NOT NULL 列没有这部分

当使用 ascii 字符集时:如果 ‘真实数据占用的字节长度’ 占用两个字节,NULL 值标识占用一个字节,则真实数据最多只能存储 65532 个字符

当使用 utf8 字符集时:如果 ‘真实数据占用的字节长度’ 占用两个字节,NULL 值标识占用一个字节,则真实数据最多只能存储 21844 (65532 / 3) 个字符

一个页一般为 16KB (16384 个字节),而一个 VERCHAR 最多可以存储 65532 个字节,所以会出现一个页存放不下一条记录的情况,造成行溢出。

REDUNDANT (冗余行模式)

使用冗余行格式的表将可变长度列值(VARCHAR、VARBINARY以及BLOB和TEXT类型)的前768个字节存储在B-tree节点内的索引记录中,其余的存储在溢出页上。大于或等于768字节的固定长度列被编码为可变长度列,可以存储在溢出页。

如果列的值为768字节或更少,则不会使用溢出页,并且可能会节省一些I/O,因为该值完全存储在B树节点中。这对于相对较短的BLOB列值很有效,但可能会导致B-tree节点填充数据而不是键值,从而降低它们的效率。包含许多BLOB列的表可能会导致B-tree节点变得太满,并且包含的行太少,从而使整个索引的效率低于行较短或列值存储在页外的情况。

  1. 每个索引记录包含一个6字节的头。用于链接连续的记录,并用于行级锁定。
  2. 聚集索引中的记录包含所有用户定义列的字段。此外,还有一个6字节的事务ID字段和一个7字节的roll pointer字段。
  3. 如果没有为表定义主键,那么每个聚集索引记录也包含一个6字节的ID字段。
  4. 一个记录包含一个指向该记录的每个字段的指针。如果记录中字段的总长度小于128字节,则指针为一个字节; 否则,两个字节。指针数组称为记录目录。指针所指向的区域是记录的数据部分。
  5. 固定长度的字符列,比如 CHAR(5) 以固定长度格式存储。不够长度以空格填充,VARCHAR 列的末尾空格不会被截断。
  6. 大于或等于768字节的固定长度列被编码为可变长度列,可以在页面外存储。
  7. SQL NULL 在记录目录中保留一个或两个字节。如果存储在可变长度列中,SQL NULL值将在记录的数据部分保留零字节。对于固定长度的列,该列的固定长度保留在记录的数据部分中。为空值保留固定空间允许将列从空值原地更新为非空值,而不会导致索引页碎片。

COMPACT (紧凑模式)

对比冗余行模式,紧凑模式在相同情况下节约了 20% 的空间,但增加了更多的 cpu 消耗。

使用冗余行格式的表将可变长度列值(VARCHAR、VARBINARY以及BLOB和TEXT类型)的前768个字节存储在B-tree节点内的索引记录中,其余的存储在溢出页上。大于或等于768字节的固定长度列被编码为可变长度列,可以存储在溢出页。

如果列的值为768字节或更少,则不会使用溢出页,并且可能会节省一些I/O,因为该值完全存储在B树节点中。这对于相对较短的BLOB列值很有效,但可能会导致B-tree节点填充数据而不是键值,从而降低它们的效率。包含许多BLOB列的表可能会导致B-tree节点变得太满,并且包含的行太少,从而使整个索引的效率低于行较短或列值存储在页外的情况。

  1. 每个索引记录包含一个5字节的头,前面可能有一个变长头。用于链接连续的记录,并用于行级锁定。
  2. 记录头的可变长度部分包含一个位向量,用于指示空列。如果索引中可以为NULL的列数为N,则位向量占用最大值(N/8)字节。例如:如果有9到16列可以为NULL,则位向量使用两个字节。
  3. 除向量中的位外,为NULL的列不占用空间。标题的变长部分还包含变长列的长度。每个长度为1或2个字节,取决于列的最大长度。如果索引中的所有列都不是空的,并且具有固定的长度,则记录头中没有可变长度的部分。
  4. 对于每个非NULL可变长度字段,记录头均以一或两个字节包含列的长度。仅当列的一部分存储在外部溢出页面中或最大长度超过255个字节且实际长度超过127个字节时,才需要两个字节。对于外部存储的列,2字节的长度表示内部存储部分的长度加上指向外部存储部分的20字节指针。内部部分是768个字节,因此长度是768 + 20。 20字节的指针存储列的真实长度。
  5. 记录头后面紧跟着非空列的数据内容。
  6. 聚集索引中的记录包含所有用户定义列的字段。此外,还有一个6字节的事务ID字段和一个7字节的roll pointer字段。
  7. 如果没有为表定义主键,那么每个聚集索引记录也包含一个6字节的行ID字段。
  8. 每个二级索引记录包含为聚集索引键定义的、不在二级索引中的所有主键列。如果任何一个主键列是可变长度的,那么每个二级索引的记录头都有一个可变长度的部分来记录它们的长度,即使二级索引是在固定长度的列上定义的。
  9. VARCHAR 列的末尾空格不会被截断。
  10. CHAR(N) 最大存储 N 个字符,超过将会切断。

DYNAMIC (动态模式)

动态行格式提供了与紧凑行格式相同的存储特性,但增加了对长变长列的存储能力,并支持大型索引键前缀。

  1. 当使用ROW_FORMAT=DYNAMIC创建表时,InnoDB可以完全在页面外存储长的可变长度列值(对于VARCHAR、VARBINARY以及BLOB和TEXT类型),聚集索引记录只包含指向溢出页的20字节指针。大于或等于768字节的固定长度字段被编码为可变长度字段。
  2. 列是否存储在页外取决于页面大小和行的总大小。当行太长时,将选择最长的列进行页外存储,直到聚集索引记录适合B树页面。小于或等于40字节的文本和BLOB列以行方式存储。
  3. 动态行格式保持了将整行存储在索引节点中的效率(紧凑和冗余格式也是如此),但动态行格式避免了用大量长列数据字节填充B-tree节点的问题。动态行格式基于这样一种想法,即如果长数据值的一部分存储在页外,则通常将整个值存储在页外是最有效的。使用动态格式时,较短的列可能保留在B树节点中,从而最大限度地减少给定行所需的溢出页数。
  4. 动态行格式最多支持3072个字节的索引键前缀。此功能由INONDB_LARGE_PREFIX变量控制,该变量在默认情况下处于禁用状态。

COMPRESSED (压缩模式)

压缩行格式提供与动态行格式相同的存储特征和功能,但增加了对表和索引数据压缩的支持。

  1. 压缩行格式添加了对表和索引数据压缩的支持。最多3072字节的索引键前缀。该特性由innodb_large_prefix 变量控制,该变量在默认情况下是禁用的。
  2. 压缩行格式对页外存储使用与动态行格式类似的内部详细信息,同时压缩表和索引数据并使用较小的页大小来考虑更多的存储和性能问题。对于压缩行格式,KEY_BLOCK_SIZE选项控制聚集索引中存储了多少列数据,以及有多少数据放置在溢出页上。
  3. 动态行格式最多支持3072个字节的索引键前缀。此功能由INONDB_LARGE_PREFIX变量控制,该变量在默认情况下处于禁用状态。
总结
  1. 当发生行溢出时,在 Compact 和 Reduntant 中,‘记录的真实数据’ 处只会存储一部分 (768字节的) 数据,剩下的数据存储在几个其他的页 (溢出页) 中 (以链表的方式连接),在 ‘记录的真实数据’ 处用 20个字节存储这些页的地址 (包含分散在其他页面中的数据的占用的字节数)。
  2. 当发生行溢出时, 不会在 ‘记录的真实数据’ 处储存真实数据的前768个字节,而是把所有的字节都存储到其他页面中,只储存其他页面的地址(20字节)。