17.10 InnoDB 行格式
表的行格式确定了其行如何物理存储,这反过来可以影响查询和DML操作的性能。随着更多行可以在单个磁盘页面中存储,查询和索引查找可以更快,缓存内存在缓冲池中所需的空间减少,并且写出更新值时需要的I/O减少。
每个表中的数据被分割成页面。构成每个表的页面是由一个称为B-树索引的树形结构组成的。表数据和次要索引都使用这种结构。整个表的B-树索引称为聚簇索引,它根据主键列组织。聚簇索引数据结构中的节点包含了行中的所有列值。次要索引结构中的节点包含了索引列和主键列的值。
可变长列除外,column值在B-树索引节点中存储。可变长列如果太长以至于不能在B-树页面上存储,将被存储在单独分配的磁盘页面上,这些页面称为溢出页面。这些列称为off-page列。off-page列的值在溢出页面的单向链表中存储,每个列都有一个或多个溢出页面的列表。根据列长度,所有或可变长列值的前缀将被存储在B-树中,以避免浪费存储空间并减少读取单独页面的需要。
InnoDB 存储引擎支持四种行格式:REDUNDANT
、COMPACT
、DYNAMIC
和COMPRESSED
。
表17.14 InnoDB 行格式概述
Row Format | Compact Storage Characteristics | Enhanced Variable-Length Column Storage | Large Index Key Prefix Support | Compression Support | Supported Tablespace Types |
---|---|---|---|---|---|
REDUNDANT |
No | No | No | 否 | 系统、文件-per-表、通用 |
COMPACT |
是 | 否 | 否 | 否 | 系统、文件-per-表、通用 |
DYNAMIC |
是 | 是 | 是 | 否 | 系统、文件-per-表、通用 |
COMPRESSED |
是 | 是 | 是 | 是 | 文件-per-表、通用 |
以下主题描述行格式存储特征和如何定义和确定表的行格式。
REDUNDANT 格式提供与 MySQL 旧版本的兼容性。
使用REDUNDANT
行格式的表格将变长列值(VARCHAR
、VARBINARY
、BLOB
和TEXT
类型)的前768个字节存储在索引记录中,剩余部分存储在溢出页面中。固定长度列大于或等于768个字节将被编码为变长列,可以存储在off-page中。例如,CHAR(255)
列可以超过768个字节,如果字符集的最大字节数量大于3,如utf8mb4。
如果列值小于或等于768个字节,无需使用溢出页面,可能会导致一些I/O节省,因为值将完全存储在B-树节点中。这对相对较短的BLOB
列值非常适用,但可能会导致B-树节点填满数据,而不是键值,降低其效率。包含许多BLOB
列的表格可能会导致B-树节点变得太满,包含太少的行,使整个索引变得不如预期那样高效。
REDUNDANT
行格式具有以下存储特点:
-
每个索引记录都包含一个6字节的头部。头部用于链接一起的连续记录,以及行级锁定。
-
聚簇索引中的记录包含所有用户定义的列字段。此外,还有一个6字节的事务ID字段和7字节的roll指针字段。
-
如果表中没有定义主键,每个聚簇索引记录还包含一个6字节的行ID字段。
-
每个次要索引记录包含聚簇索引关键字中定义的所有主键列字段,但这些列字段不在次要索引中。
-
记录中包含指向每个字段的指针。如果记录中的字段总长度小于128字节,指针为1字节;否则,为2字节。这个数组称为记录目录。指针所指的区域是记录的数据部分。
-
大于或等于768字节的固定长度列将被编码为可变长度列,可以存储在off-page中。例如,
CHAR(255)
列可以超过768字节,如果字符集的最大字节数量大于3,如utf8mb4
。 -
SQL
NULL
值在记录目录中占用 1 或 2 个字节。SQLNULL
值在变长列中存储时,在记录的数据部分中不占用任何字节。如果是固定长度列,则在记录的数据部分中占用该列的固定长度。为 NULL 值预留固定空间使得可以从 NULL 值更新到非 NULL 值而不导致索引页面碎片。
COMPACT 行格式通过将行存储空间减少约 20%,相比 REDUNDANT 行格式,但是在某些操作中增加 CPU 使用。如果您的工作负载是受缓存命中率和磁盘速度限制的,那么 COMPACT 格式可能更快。如果工作负载受到 CPU 速度限制,compact 格式可能会慢一些。
使用COMPACT
行格式的表格将变长列值(VARCHAR
、VARBINARY
、BLOB
和TEXT
类型)在B-树节点中的前768个字节中存储,剩余部分存储于溢出页面。固定长度列大于或等于768个字节将被编码为变长列,可以存储在off-page上。例如,CHAR(255)
列可以超过768个字节,如果字符集的最大字节数量大于3,如utf8mb4。
如果列值小于或等于768个字节,无需使用溢出页面,可能会导致一些I/O节省,因为值将完全存储在B-树节点中。这对相对较短的BLOB
列值非常适用,但可能会导致B-树节点填满数据,而不是键值,降低其效率。包含多个BLOB
列的表格可能会导致B-树节点变得太满,包含太少的行,使整个索引变得不如预期那样高效。
COMPACT
行格式具有以下存储特性:
-
每个索引记录包含一个5字节的头部,可能会被前置的可变长度头部所preceded。头部用于链接一起的连续记录,以及行级锁定。
-
记录头部的可变部分包含一个位向量,用于指示
NULL
列。如果索引中可以为NULL
的列数为N
,那么位向量占用CEILING(
个字节。例如,如果有9到16个可以为N
/8)NULL
的列,那么位向量使用两个字节。为NULL
的列不占用除位向量之外的空间。记录头部的可变部分还包含变长列的长度,每个长度占用一个或两个字节,取决于列的最大长度。如果索引中的所有列都是NOT NULL
且具有固定长度,那么记录头部就没有可变部分。 -
对于每个非
NULL
变长字段,记录头部包含该列的长度,在一个或两个字节中。只有当部分列存储在溢出页面中或最大长度超过255字节且实际长度超过127字节时,才需要两个字节。如果是externally stored column,那么2个字节的长度表示内部存储部分的长度加上20字节的指向外部存储部分的指针。内部部分为768字节,所以长度为768+20。20字节的指针存储该列的真实长度。 -
记录头部后跟随非
NULL
列的数据内容。 -
聚簇索引中的记录包含用户定义的所有字段。此外,还有一个6字节的事务ID字段和7字节的roll指针字段。
-
如果表中没有定义主键,每个聚簇索引记录还包含一个6字节的行ID字段。
-
每个次要索引记录都包含了聚集索引键中定义的所有主键列,但这些列不在次要索引中。如果任何主键列是可变长度的,那么每个次要索引记录的头部都有一个可变长部分来记录它们的长度,即使次要索引是定义在固定长度列上的。
-
对于非可变字符集,固定长度字符列,如
CHAR(10)
,内部存储在固定长度格式中。尾部空格不会被截断来自
VARCHAR
列。 -
对于可变长度字符集,如
utf8mb3
和utf8mb4
,InnoDB
尝试将CHAR(
存储在N
)N
个字节中,通过截断尾部空格。如果CHAR(
列值的字节长度超过N
)N
个字节,尾部空格将被截断到列值的最大字节长度。一个CHAR(
列的最大长度是字符字节长度×N
)N
。为
CHAR(
保留了至少 <code>N</code> 字节的空间。在许多情况下,这样做可以在不导致索引页面碎片的情况下对列进行 in-place 更新。与之相比,使用N
)REDUNDANT
行格式时,CHAR(
列在存储时占用的是字符集的最大字节长度 × <code>N</code>。N
)固定长度列如果大于或等于 768 字节将被编码为可变长度字段,这些字段可以存储在 off-page 中。例如,一个
CHAR(255)
列如果字符集的最大字节长度大于 3,可以超过 768 字节,因为 utf8mb4 是这样一种字符集。
DYNAMIC 行格式提供了与 COMPACT 行格式相同的存储特性,但添加了对长可变长度列和大索引键前缀的存储能力支持。
当使用ROW_FORMAT=DYNAMIC
创建表时,InnoDB
可以将长变长列值(包括VARCHAR
、VARBINARY
、BLOB
和TEXT
类型)完全存储在off-page中,clustered index记录只包含一个20字节的指向overflow page的指针。固定长度字段大于或等于768字节将被编码为变长字段。例如,CHAR(255)
列可以超过768字节,如果字符集的最大字节数量大于3,如utf8mb4。
是否将列存储在off-page中取决于页面大小和行总大小。当一行太长时,选择最长的列进行off-page存储直到clustered index记录可以存储在B-树页面中。TEXT
和BLOB
列小于或等于40字节将被存储在行中。
动态行格式(DYNAMIC
)保持了存储整个行在索引节点中的效率,如果它适合(与COMPACT
和REDUNDANT
格式相同),但是动态行格式避免了填充B树节点的长列数据问题。动态行格式基于的想法是,如果一个长数据值的一部分存储在off-page中,那么通常最有效的是将整个值存储在off-page中。使用DYNAMIC
格式,较短的列可能仍然留在B树节点中,从而减少了给定行所需的溢出页面数量。
动态行格式支持索引键前缀最长为3072字节。
使用动态行格式的表可以存储在系统表空间、文件表空间和公共表空间中。要将DYNAMIC
表存储在系统表空间中,可以禁用innodb_file_per_table
并使用常规的CREATE TABLE
或ALTER TABLE
语句,或者使用TABLESPACE [=] innodb_system
表选项与CREATE TABLE
或ALTER TABLE
。变量innodb_file_per_table
不适用于公共表空间,也不适用于使用TABLESPACE [=] innodb_system
表选项将DYNAMIC
表存储在系统表空间中的情况。
动态行格式(DYNAMIC
)是 COMPACT 行格式的变体。关于存储特性,请参阅COMPACT Row Format Storage Characteristics。
压缩行格式(COMPRESSED
)提供了与动态行格式相同的存储特性和功能,但添加了对表和索引数据压缩的支持。
压缩行格式使用类似的内部细节来存储离页数据,同样具有动态行格式的存储和性能考虑因素。由于表和索引数据被压缩并使用较小的页面大小,因此压缩行格式中KEY_BLOCK__SIZE
选项控制了聚簇索引中的列数据存储量,以及溢出页上的存储量。关于压缩行格式的更多信息,请参阅第17.9节,“InnoDB 表和页面压缩”。
压缩行格式支持索引键前缀最长为3072字节。
使用COMPRESSED
行格式的表可以在文件表空间或公共表空间中创建。系统表空间不支持COMPRESSED
行格式。在存储一个COMPRESSED
表到文件表空间中,需要启用innodb_file_per_table
变量。对公共表空间不适用innodb_file_per_table
变量。公共表空间支持所有行格式,但需要注意压缩和未压缩表不能在同一个公共表空间中共存,因为它们具有不同的物理页面大小。更多信息,请见第17.6.3.3节,“公共表空间”。
COMPRESSED
行格式是COMPACT
行格式的变体。存储特性,请见COMPACT Row Format Storage Characteristics。
InnoDB表的默认行格式由innodb_default_row_format
变量定义,该变量的默认值为DYNAMIC
。当没有明确指定ROW_FORMAT
表选项或指定ROW_ FORMAT=DEFAULT
时,使用默认行格式。
可以使用CREATE TABLE
或ALTER TABLE
语句中的ROW_FORMAT
表选项来明确地定义表的行格式。例如:
CREATE TABLE t1 (c1 INT) ROW_FORMAT=DYNAMIC;
明确定义的ROW_FORMAT
设置将覆盖默认行格式。指定ROW_FORMAT=DEFAULT
等同于使用隐式默认值。
可以动态地设置innodb_default_row_format
变量:
mysql> SET GLOBAL innodb_default_row_format=DYNAMIC;
有效的innodb_default_row_format
选项包括DYNAMIC
、COMPACT
和REDUNDANT
。不能将COMPRESSED
行格式指定为默认值。只能在CREATE TABLE
或ALTER TABLE
语句中明确指定。尝试将innodb_default_row_format
变量设置为COMPRESSED
将返回错误:
mysql> SET GLOBAL innodb_default_row_format=COMPRESSED;
ERROR 1231 (42000): Variable 'innodb_default_row_format'
can't be set to the value of 'COMPRESSED'
新创建的表使用innodb_default_row_format
变量定义的行格式,当没有明确指定ROW_FORMAT
选项或使用ROW_ FORMAT=DEFAULT
时。例如,以下CREATE TABLE
语句使用innodb_default_row_format
变量定义的行格式。
CREATE TABLE t1 (c1 INT);
CREATE TABLE t2 (c1 INT) ROW_FORMAT=DEFAULT;
当没有明确指定ROW_FORMAT
选项或使用ROW_ FORMAT=DEFAULT
时,重建表的操作悄然将表的行格式更改为innodb_default_row_format
变量定义的行格式。
重建表的操作包括使用ALGORITHM=COPY
或ALGORITHM=INPLACE
的ALTER TABLE
操作,以及需要重建表的其他操作。请参阅第17.12.1节,“在线DDL操作”获取更多信息。OPTIMIZE TABLE
也是重建表的操作。
以下示例演示了一个悄然更改行格式的重建表操作,该操作是在没有明确指定行格式的情况下创建的表。
mysql> SELECT @@innodb_default_row_format;
+-----------------------------+
| @@innodb_default_row_format |
+-----------------------------+
| dynamic |
+-----------------------------+
mysql> CREATE TABLE t1 (c1 INT);
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME LIKE 'test/t1' \G
*************************** 1. row ***************************
TABLE_ID: 54
NAME: test/t1
FLAG: 33
N_COLS: 4
SPACE: 35
ROW_FORMAT: Dynamic
ZIP_PAGE_SIZE: 0
SPACE_TYPE: Single
mysql> SET GLOBAL innodb_default_row_format=COMPACT;
mysql> ALTER TABLE t1 ADD COLUMN (c2 INT);
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME LIKE 'test/t1' \G
*************************** 1. row ***************************
TABLE_ID: 55
NAME: test/t1
FLAG: 1
N_COLS: 5
SPACE: 36
ROW_FORMAT: Compact
ZIP_PAGE_SIZE: 0
SPACE_TYPE: Single
在将现有表的行格式从REDUNDANT
或COMPACT
更改为DYNAMIC
之前,需要考虑以下潜在问题。
-
REDUNDANT
和COMPACT
行格式支持索引键前缀长度的最大值为767字节,而DYNAMIC
和COMPRESSED
行格式支持索引键前缀长度的最大值为3072字节。在复制环境中,如果源服务器上的innodb_default_row_format
变量设置为DYNAMIC
,而副本服务器上的变量设置为COMPACT
,以下DDL语句将在源服务器上成功,但在副本服务器上失败:CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 VARCHAR(5000), KEY i1(c2(3070)));
有关相关信息,请见第17.21节,“InnoDB Limits”。
-
如果不明确定义行格式的表导入结果将导致架构不匹配错误,如果源服务器上的
innodb_default_row_format
设置与目标服务器上的设置不同。有关更多信息,请见第17.6.1.3节,“Importing InnoDB Tables”。
要确定表的行格式,请使用SHOW TABLE STATUS
:
mysql> SHOW TABLE STATUS IN test1\G
*************************** 1. row ***************************
Name: t1
Engine: InnoDB
Version: 10
Row_format: Dynamic
Rows: 0
Avg_row_length: 0
Data_length: 16384
Max_data_length: 0
Index_length: 16384
Data_free: 0
Auto_increment: 1
Create_time: 2016-09-14 16:29:38
Update_time: NULL
Check_time: NULL
Collation: utf8mb4_0900_ai_ci
Checksum: NULL
Create_options:
Comment:
或者,您可以查询信息_schema中的INNODB_TABLES
表:
mysql> SELECT NAME, ROW_FORMAT FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME='test1/t1';
+----------+------------+
| NAME | ROW_FORMAT |
+----------+------------+
| test1/t1 | Dynamic |
+----------+------------+