表的行格式决定了其行在物理上是如何存储的,这反过来会影响查询和 DML 操作的性能。随着更多行可以放入单个磁盘页中,查询和索引查找可以更快地完成,缓冲池中需要更少的缓存内存,并且写入更新值的 I/O 操作也更少。
每个表中的数据都划分成页。构成每个表的页面排列在一个树形数据结构中,称为 B 树索引。表数据和辅助索引都使用这种类型的结构。表示整个表的 B 树索引称为聚簇索引,它根据主键列组织。聚簇索引数据结构的节点包含行中所有列的值。辅助索引结构的节点包含索引列和主键列的值。
可变长度列是列值存储在 B 树索引节点中的规则的例外。超出 B 树页大小的可变长度列存储在单独分配的磁盘页上,称为溢出页。这种列被称为离页列。离页列的值存储在溢出页的单链表中,每个此类列都有自己一个或多个溢出页的列表。根据列的长度,所有或可变长度列值的开头部分都存储在 B 树中,以避免浪费存储空间,并且不必读取单独的页。
InnoDB
存储引擎支持四种行格式:REDUNDANT
、COMPACT
、DYNAMIC
和 COMPRESSED
。
表 17.14 InnoDB 行格式概述
行格式 | 紧凑存储特性 | 增强的可变长度列存储 | 支持大型索引键前缀 | 压缩支持 | 支持的表空间类型 |
---|---|---|---|---|---|
REDUNDANT |
否 | 否 | 否 | 否 | system、file-per-table、general |
COMPACT |
是 | 否 | 否 | 否 | system、file-per-table、general |
DYNAMIC |
是 | 是 | 是 | 否 | system、file-per-table、general |
COMPRESSED |
是 | 是 | 是 | 是 | file-per-table、general |
以下主题描述了行格式存储特性以及如何定义和确定表的行格式。
REDUNDANT
格式提供与旧版 MySQL 的兼容性。
使用 REDUNDANT
行格式的表将可变长度列值的前 768 字节(VARCHAR
、VARBINARY
以及 BLOB
和 TEXT
类型)存储在 B 树节点内的索引记录中,其余部分存储在溢出页上。大于或等于 768 字节的固定长度列被编码为可变长度列,可以存储在离页位置。例如,如果字符集的最大字节长度大于 3(例如 utf8mb4
),则 CHAR(255)
列可以超过 768 字节。
如果列的值为 768 字节或更小,则不会使用溢出页,并且可能会节省一些 I/O 操作,因为该值完全存储在 B 树节点中。这对相对较短的 BLOB
列值非常有效,但可能会导致 B 树节点充满数据而不是键值,从而降低它们的效率。具有许多 BLOB
列的表可能会导致 B 树节点变得过于饱满,并且包含的行太少,使整个索引的效率低于行更短或列值存储在离页位置时的效率。
REDUNDANT 行格式存储特性
REDUNDANT
行格式具有以下存储特性
每个索引记录包含一个 6 字节的标头。该标头用于将连续的记录链接在一起,并用于行级锁定。
聚簇索引中的记录包含所有用户定义列的字段。此外,还有一个 6 字节的事务 ID 字段和一个 7 字节的回滚指针字段。
如果表没有定义主键,则每个聚集索引记录还包含一个 6 字节的行 ID 字段。
每个辅助索引记录包含所有为聚集索引键定义的主键列,这些主键列不在辅助索引中。
记录包含指向记录每个字段的指针。如果记录中字段的总长度小于 128 字节,则指针为 1 字节;否则为 2 字节。指针数组称为记录目录。指针指向的区域是记录的数据部分。
大于或等于 768 字节的固定长度列被编码为可变长度列,可以存储在页面之外。例如,如果字符集的最大字节长度大于 3(如
utf8mb4
),则CHAR(255)
列可以超过 768 字节。SQL
NULL
值在记录目录中保留一个或两个字节。如果存储在可变长度列中,则 SQLNULL
值在记录的数据部分中保留零个字节。对于固定长度列,在记录的数据部分中保留列的固定长度。为NULL
值保留固定空间允许在不导致索引页面碎片的情况下将列从NULL
值更新为非NULL
值。
与 REDUNDANT
行格式相比,COMPACT
行格式减少了大约 20% 的行存储空间,但代价是增加了某些操作的 CPU 使用率。如果您的工作负载是典型的,受缓存命中率和磁盘速度的限制,则 COMPACT
格式可能更快。如果工作负载受 CPU 速度的限制,则紧凑格式可能更慢。
使用 COMPACT
行格式的表将可变长度列值的前 768 字节(VARCHAR
、VARBINARY
以及 BLOB
和 TEXT
类型)存储在 B 树 节点中的索引记录中,剩余部分存储在溢出页面上。大于或等于 768 字节的固定长度列被编码为可变长度列,可以存储在页面之外。例如,如果字符集的最大字节长度大于 3(如 utf8mb4
),则 CHAR(255)
列可以超过 768 字节。
如果列的值为 768 字节或更小,则不会使用溢出页,并且可能会节省一些 I/O 操作,因为该值完全存储在 B 树节点中。这对相对较短的 BLOB
列值非常有效,但可能会导致 B 树节点充满数据而不是键值,从而降低它们的效率。具有许多 BLOB
列的表可能会导致 B 树节点变得过于饱满,并且包含的行太少,使整个索引的效率低于行更短或列值存储在离页位置时的效率。
COMPACT 行格式存储特性
COMPACT
行格式具有以下存储特性
每个索引记录包含一个 5 字节的报头,报头前面可能是一个可变长度报头。报头用于链接连续记录,以及用于行级锁定。
记录报头的可变长度部分包含一个位向量,用于指示
NULL
列。如果索引中可以为NULL
的列数为N
,则位向量占用CEILING(
字节。(例如,如果有 9 到 16 列可以为N
/8)NULL
,则位向量使用 2 个字节。)NULL
的列除了此向量中的位之外不占用空间。报头的可变长度部分还包含可变长度列的长度。每个长度占用一个或两个字节,具体取决于列的最大长度。如果索引中的所有列都是NOT NULL
并且具有固定长度,则记录报头没有可变长度部分。对于每个非
NULL
可变长度字段,记录报头包含列的长度(一个或两个字节)。只有当列的一部分存储在溢出页面上或最大长度超过 255 字节并且实际长度超过 127 字节时,才需要两个字节。对于外部存储列,2 字节长度表示内部存储部分的长度加上指向外部存储部分的 20 字节指针。内部部分为 768 字节,因此长度为 768+20。20 字节指针存储列的真实长度。记录报头后面是非
NULL
列的数据内容。聚簇索引中的记录包含所有用户定义列的字段。此外,还有一个 6 字节的事务 ID 字段和一个 7 字节的回滚指针字段。
如果表没有定义主键,则每个聚集索引记录还包含一个 6 字节的行 ID 字段。
每个辅助索引记录包含所有为聚集索引键定义的主键列,这些主键列不在辅助索引中。如果任何主键列是可变长度,则每个辅助索引的记录报头都具有可变长度部分来记录它们的长度,即使辅助索引是在固定长度列上定义的。
在内部,对于非可变长度字符集,固定长度字符列(例如
CHAR(10)
)以固定长度格式存储。不会从
VARCHAR
列中截断尾随空格。在内部,对于可变长度字符集(例如
utf8mb3
和utf8mb4
),InnoDB
尝试在N
字节中存储CHAR(
,方法是修剪尾随空格。如果N
)CHAR(
列值的字节长度超过N
)N
字节,则尾随空格将被修剪到列值字节长度的最大值。CHAR(
列的最大长度为最大字符字节长度 ×N
)N
。为
CHAR(
保留至少N
)N
字节。在许多情况下,保留最小空间N
使得列更新能够就地完成,而不会导致索引页面碎片。相比之下,CHAR(
列在使用N
)REDUNDANT
行格式时占用最大字符字节长度 ×N
。大于或等于 768 字节的固定长度列被编码为可变长度字段,可以存储在页面之外。例如,如果字符集的最大字节长度大于 3(如
utf8mb4
),则CHAR(255)
列可以超过 768 字节。
DYNAMIC
行格式提供了与 COMPACT
行格式相同的存储特性,但增加了对长可变长度列的增强存储功能,并支持大型索引键前缀。
当使用 ROW_FORMAT=DYNAMIC
创建表时,InnoDB
可以将长可变长度列值(对于 VARCHAR
、VARBINARY
以及 BLOB
和 TEXT
类型)完全存储在页面之外,聚集索引记录中只包含指向溢出页面的 20 字节指针。大于或等于 768 字节的固定长度字段被编码为可变长度字段。例如,如果字符集的最大字节长度大于 3(如 utf8mb4
),则 CHAR(255)
列可以超过 768 字节。
列是否存储在页面之外取决于页面大小和行的总大小。当行太长时,会选择最长的列进行页面外存储,直到聚集索引记录适合 B 树 页面。小于或等于 40 字节的 TEXT
和 BLOB
列将存储在线上。
DYNAMIC
行格式保持了在索引节点中存储整个行的效率(如果适合,COMPACT
和 REDUNDANT
格式也是如此),但是 DYNAMIC
行格式避免了用大量长列数据字节填充 B 树节点的问题。 DYNAMIC
行格式基于这样一种理念:如果长数据值的一部分存储在页面之外,则通常将整个值存储在页面之外效率最高。使用 DYNAMIC
格式,较短的列很可能保留在 B 树节点中,从而最大限度地减少给定行所需的溢出页面数量。
DYNAMIC
行格式支持最大 3072 字节的索引键前缀。
使用 DYNAMIC
行格式的表可以存储在系统表空间、每表文件表空间和通用表空间中。若要将 DYNAMIC
表存储在系统表空间中,请禁用 innodb_file_per_table
并使用常规的 CREATE TABLE
或 ALTER TABLE
语句,或者在使用 CREATE TABLE
或 ALTER TABLE
时使用 TABLESPACE [=] innodb_system
表选项。 innodb_file_per_table
变量不适用于通用表空间,也不适用于使用 TABLESPACE [=] innodb_system
表选项将 DYNAMIC
表存储在系统表空间中时。
DYNAMIC 行格式存储特性
DYNAMIC
行格式是 COMPACT
行格式的一种变体。有关存储特性,请参阅 COMPACT 行格式存储特性。
COMPRESSED
行格式提供了与 DYNAMIC
行格式相同的存储特性和功能,但增加了对表和索引数据压缩的支持。
COMPRESSED
行格式使用与 DYNAMIC
行格式类似的内部详细信息进行页面外存储,并根据表和索引数据进行压缩以及使用更小的页面大小,增加了存储和性能方面的考虑因素。使用 COMPRESSED
行格式时,KEY_BLOCK_SIZE
选项控制在聚集索引中存储多少列数据,以及将多少列数据放置在溢出页面上。有关 COMPRESSED
行格式的更多信息,请参阅 第 17.9 节,“InnoDB 表和页面压缩”。
COMPRESSED
行格式支持最大 3072 字节的索引键前缀。
使用 COMPRESSED
行格式的表可以在每个表文件表空间或通用表空间中创建。系统表空间不支持 COMPRESSED
行格式。要将 COMPRESSED
表存储在每个表文件表空间中,必须启用 innodb_file_per_table
变量。 innodb_file_per_table
变量不适用于通用表空间。通用表空间支持所有行格式,但压缩表和未压缩表不能在同一个通用表空间中共存,因为它们的物理页面大小不同。有关更多信息,请参见 第 17.6.3.3 节,“通用表空间”。
压缩行格式存储特性
COMPRESSED
行格式是 COMPACT
行格式的变体。有关存储特性的信息,请参见 COMPACT 行格式存储特性。
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'
当未明确指定 ROW_FORMAT
选项,或使用 ROW_FORMAT=DEFAULT
时,新创建的表将使用 innodb_default_row_format
变量定义的行格式。例如,以下 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
,而副本服务器上的innodb_default_row_format
变量设置为COMPACT
,则以下 DDL 语句(未明确定义行格式)在源服务器上成功,但在副本服务器上失败CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 VARCHAR(5000), KEY i1(c2(3070)));
有关相关信息,请参见 第 17.21 节,“InnoDB 限制”。
如果源服务器上的
innodb_default_row_format
设置不同于目标服务器上的设置,则导入未明确定义行格式的表会导致模式不匹配错误。有关更多信息,请参见 第 17.6.1.3 节,“导入 InnoDB 表”。
要确定表的行格式,请使用 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:
或者,查询信息架构 INNODB_TABLES
表
mysql> SELECT NAME, ROW_FORMAT FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME='test1/t1';
+----------+------------+
| NAME | ROW_FORMAT |
+----------+------------+
| test1/t1 | Dynamic |
+----------+------------+