表的行格式决定了其行如何物理存储,这反过来会影响查询和 DML 操作的性能。随着更多的行适合单个磁盘页,查询和索引查找可以更快,缓存池中需要的内存较少,写出更新值所需的 I/O 也较少。
每个表的数据被分割成页。组成每个表的页被安排在一个树形数据结构中,称为 B 树索引。表数据和次要索引都使用这种结构。代表整个表的 B 树索引称为聚簇索引,根据主键列组织。聚簇索引数据结构的节点包含行中的所有列值。次要索引结构的节点包含索引列和主键列的值。
可变长度列是 B 树索引节点中存储列值的规则的例外。太长以致无法适合 B 树页的可变长度列被存储在单独分配的磁盘页上,称为溢出页。这样的列被称为溢出列。溢出列的值被存储在单链表的溢出页中,每个溢出列都有其自己的溢出页链表。根据列长度,所有或可变长度列值的前缀都被存储在 B 树中,以避免浪费存储空间和读取单独的页。
InnoDB 存储引擎支持四种行格式:REDUNDANT
、COMPACT
、DYNAMIC
和 COMPRESSED
。
表 17.15 InnoDB 行格式概述
Row Format | Compact Storage Characteristics | Enhanced Variable-Length Column Storage | Large Index Key Prefix Support | Compression Support | Supported Tablespace Types |
---|---|---|---|---|---|
REDUNDANT |
否 | 否 | 否 | 否 | 系统、每个表、通用 |
COMPACT |
是 | 否 | 否 | 否 | 系统、每个表、通用 |
DYNAMIC |
是 | 是 | 是 | 否 | 系统、每个表、通用 |
COMPRESSED |
是 | 是 | 是 | 是 | 每个表、通用 |
以下主题将描述行格式存储特征和如何定义和确定表的行格式。
REDUNDANT 格式提供了与旧版本 MySQL 的兼容性。
使用 REDUNDANT 行格式的表将可变长度列值的前 768 字节 (VARCHAR
、VARBINARY
和 BLOB
和 TEXT
类型) 存储在 B 树节点中的索引记录中,其余部分存储在溢出页上。固定长度列大于或等于 768 字节被编码为可变长度列,可以存储在溢出页上。例如,CHAR(255)
列可能超过 768 字节,如果字符集的最大字节长度大于 3,如 utf8mb4。
如果列值的长度为 768 字节或更少,不使用溢出页,这可能会导致 I/O 的一些节省,因为值被完全存储在 B 树节点中。这对相对较短的 BLOB
列值非常有效,但可能会导致 B 树节点填充数据而不是键值,减少索引的效率。具有许多 BLOB
列的表可能会导致 B 树节点变得太满,包含太少的行,使整个索引变得不太有效。
REDUNDANT 行格式存储特征
REDUNDANT 行格式具有以下存储特征:
-
每个索引记录包含 6 字节的头部。头部用于链接连续的记录,并用于行级锁定。
-
聚集索引记录包含所有用户定义的列。此外,还有一个 6 字节的事务 ID 字段和一个 7 字节的回滚指针字段。
-
如果表没有定义主键,每个聚集索引记录还包含一个 6 字节的行 ID 字段。
-
每个次索引记录包含聚集索引键中定义的所有主键列,但不在次索引中。
-
记录包含指向每个字段的指针。如果记录中的字段总长度小于 128 字节,指针为 1 字节;否则为 2 字节。指针数组称为记录目录。指针所指向的区域是记录的数据部分。
-
固定长度列大于或等于 768 字节将被编码为变长列,可以存储在页面外。例如,
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 字节将被编码为变长列,可以存储在页面外。例如,CHAR(255)
列可以超过 768 字节,如果字符集的最大字节长度大于 3,如 utf8mb4
。
如果列值小于或等于 768 字节,不使用溢出页面,可能会减少 I/O,因为值完全存储在 B 树节点中。这对相对较短的 BLOB
列值非常有用,但可能会导致 B 树节点填充数据而不是键值,减少索引效率。具有许多 BLOB
列的表可能会导致 B 树节点变得太满,包含太少的行,降低整个索引的效率。
COMPACT 行格式存储特征
COMPACT 行格式具有以下存储特征:
-
每个索引记录包含一个 5 字节的头部,可能在变长头部之前。头部用于链接连续的记录,并用于行级锁定。
-
变长部分的记录头包含一个位向量,用于指示
NULL
列。如果索引中的可空列数为N
,则位向量占用CEILING(
字节。(例如,如果有 9 到 16 个可空列,位向量使用两个字节。)可空列不占用除位向量中的位以外的空间。记录头的变长部分还包含变长列的长度。每个长度占用一个或两个字节,取决于列的最大长度。如果所有索引列都是N
/8)NOT NULL
且长度固定,则记录头没有变长部分。 -
对于每个非
NULL
变长字段,记录头包含该列的长度,占用一个或两个字节。只有当列的一部分存储在溢出页中或最大长度超过 255 字节且实际长度超过 127 字节时,才需要两个字节。对于外部存储的列,2 字节的长度指示内部存储部分的长度加上 20 字节的指针指向外部存储部分。内部部分为 768 字节,因此长度为 768+20。20 字节的指针存储列的真实长度。 -
记录头后面是非
NULL
列的数据内容。 -
聚簇索引记录包含所有用户定义的列。此外,还有一个 6 字节的事务 ID 字段和一个 7 字节的回滚指针字段。
-
如果表没有定义主键,每个聚簇索引记录还包含一个 6 字节的行 ID 字段。
-
每个次要索引记录包含聚簇索引键中定义的所有主键列,但这些列不在次要索引中。如果任何主键列是变长的,次要索引记录头将包含一个变长部分来记录它们的长度,即使次要索引是定义在固定长度列上的。
-
内部,对于非变长字符集,固定长度字符列,如
CHAR(10)
,以固定长度格式存储。尾随空格不会从
VARCHAR
列中截断。 -
内部,对于变长字符集,如
utf8mb3
和utf8mb4
,InnoDB
尝试将CHAR(
存储在N
)N
字节中,通过截断尾随空格。如果CHAR(
列值的字节长度超过N
)N
字节,尾随空格将被截断到列值字节长度的最大值。CHAR(
列的最大长度是最大字符字节长度 ×N
)N
。至少保留
N
字节用于CHAR(
。保留最少N
)N
字节空间使得列更新可以在不引起索引页碎片的情况下进行。在比较中,CHAR(
列在使用N
)REDUNDANT
行格式时占用最大字符字节长度 ×N
。固定长度列大于或等于 768 字节将被编码为变长字段,可以存储在页外。例如,
CHAR(255)
列可以超过 768 字节,如果字符集的最大字节长度大于 3,如utf8mb4
。
动态行格式提供了与紧凑行格式相同的存储特性,但添加了对长变长列的增强存储能力,并支持大索引键前缀。
当表创建时使用 ROW_FORMAT=DYNAMIC
,InnoDB
可以将长变长列值(对于 VARCHAR
、VARBINARY
和 BLOB
和 TEXT
类型)完全存储在溢出页上,聚集索引记录仅包含 20 字节的指针指向溢出页。固定长度字段大于或等于 768 字节将被编码为变长字段。例如,CHAR(255)
列可以超过 768 字节,如果字符集的最大字节长度大于 3,如 utf8mb4
。
列是否存储在溢出页上取决于页大小和行的总大小。当行太长时,选择最长的列进行溢出页存储,直到聚集索引记录适合 B 树页。TEXT
和 BLOB
列小于或等于 40 字节将存储在行中。
动态行格式维持了将整个行存储在索引节点中的效率(与紧凑和冗余格式相同),但动态行格式避免了 B 树节点被大量数据字节填充的问题。动态行格式基于这样一个想法:如果长数据值的一部分存储在溢出页上,那么通常最好将整个值存储在溢出页上。使用动态格式,较短的列更可能留在 B 树节点中,减少了溢出页的数量。
动态行格式支持索引键前缀最多 3072 字节。
使用动态行格式的表可以存储在系统表空间、每个表的表空间和通用表空间中。要在系统表空间中存储动态表,禁用 innodb_file_per_table
并使用常规的 CREATE TABLE
或 ALTER TABLE
语句,或者使用 TABLESPACE [=] innodb_system
表选项与 CREATE TABLE
或 ALTER TABLE
。变量 innodb_file_per_table
不适用于通用表空间,也不适用于使用 TABLESPACE [=] innodb_system
表选项将动态表存储在系统表空间中。
动态行格式存储特征
动态行格式是紧凑行格式的变体。有关存储特征,请参阅 紧凑行格式存储特征。
压缩行格式提供了与动态行格式相同的存储特征和功能,但添加了表和索引数据压缩支持。
压缩行格式使用与动态行格式相似的内部细节来存储溢出页,另外还考虑了表和索引数据压缩和使用较小的页大小的存储和性能问题。使用压缩行格式,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 行格式存储特征。
InnoDB 表的默认行格式由 innodb_default_row_format
变量定义,默认值为 DYNAMIC
。默认行格式用于未明确定义 ROW_FORMAT
表选项或指定 ROW_FORMAT=DEFAULT
时。
可以使用 ROW_FORMAT
表选项在 CREATE TABLE
或 ALTER TABLE
语句中明确定义表的行格式。例如:
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 限制”。
-
导入没有明确定义行格式的表将导致模式不匹配错误,如果源服务器上的
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 |
+----------+------------+