#### 15.9.1.5 InnoDB 表的压缩如何工作

[](<>)

本节描述了一些内部实现细节[压缩](glossary.html#glos_compression)对于 InnoDB 表。此处提供的信息可能有助于调整性能，但对于压缩的基本使用没有必要了解。

##### 压缩算法

[](<>)

一些操作系统在文件系统级别实现压缩。文件通常被分成固定大小的块，这些块被压缩成可变大小的块，这很容易导致碎片。每次修改块内的某些内容时，都会在将整个块写入磁盘之前对其进行重新压缩。这些特性使得这种压缩技术不适合在更新密集型数据库系统中使用。

MySQL在众所周知的帮助下实现压缩[zlib 库](http://www.zlib.net/)，实现了LZ77压缩算法。这种压缩算法在 CPU 利用率和减少数据大小方面都是成熟、稳健和高效的。该算法是“无损”的，因此始终可以从压缩形式中重建原始未压缩数据。LZ77 压缩通过查找在要压缩的数据中重复的数据序列来工作。数据中的值模式决定了它的压缩程度，但典型的用户数据通常会压缩 50% 或更多。

笔记

`InnoDB`支持`zlib`库到版本 1.2.11，这是与 MySQL 8.0 捆绑在一起的版本。

与应用程序执行的压缩或某些其他数据库管理系统的压缩功能不同，InnoDB 压缩既适用于用户数据，也适用于索引。在许多情况下，索引可以构成总数据库大小的 40-50% 或更多，因此这种差异是显着的。当数据集的压缩工作良好时，InnoDB 数据文件的大小（[每表文件](glossary.html#glos_file_per_table)表空间或[通用表空间](glossary.html#glos_general_tablespace) `.ibd`文件）是未压缩大小的 25% 到 50% 或可能更小。取决于[工作量](glossary.html#glos_workload)，这个较小的数据库可以反过来导致 I/O 的减少和吞吐量的增加，在增加 CPU 利用率方面的成本适中。您可以通过修改压缩级别和 CPU 开销之间的平衡来调整[`innodb_compression_level`](innodb-parameters.html#sysvar_innodb_compression_level)配置选项。

##### InnoDB 数据存储和压缩

[](<>)

InnoDB 表中的所有用户数据都存储在包含一个[B树](glossary.html#glos_b_tree)指数（[聚集索引](glossary.html#glos_clustered_index)）。在其他一些数据库系统中，这种类型的索引称为“索引组织表”。索引节点中的每一行都包含（用户指定的或系统生成的）的值[首要的关键](glossary.html#glos_primary_key)以及表格的所有其他列。

[二级索引](glossary.html#glos_secondary_index)InnoDB 中的表也是 B 树，包含成对的值：索引键和指向聚集索引中行的指针。指针实际上就是表的主键的值，如果需要索引键和主键以外的列，则用于访问聚集索引。二级索引记录必须始终适合单个 B 树页面。

B-tree 节点（聚集索引和二级索引）的压缩与[溢出页面](glossary.html#glos_overflow_page)用来存放很长时间`VARCHAR`,`斑点`， 要么`文本`列，如以下部分所述。

##### B-Tree 页面的压缩

[](<>)

因为它们经常更新，所以 B 树页面需要特殊处理。重要的是尽量减少 B 树节点被拆分的次数，以及尽量减少解压缩和重新压缩其内容的需要。

MySQL 使用的一种技术是以未压缩的形式在 B-tree 节点中维护一些系统信息，从而促进某些就地更新。例如，这允许在不进行任何压缩操作的情况下对行进行删除标记和删除。

此外，MySQL 试图避免索引页在更改时不必要的解压缩和重新压缩。在每个 B-tree 页面中，系统都会保留一个未压缩的“修改日志”来记录对页面所做的更改。可以将小记录的更新和插入写入此修改日志，而无需完全重建整个页面。

当修改日志的空间用完时，InnoDB 解压缩页面，应用更改并重新压缩页面。如果重新压缩失败（这种情况称为[压缩失败](glossary.html#glos_compression_failure))，B-tree 节点被分裂并重复该过程，直到更新或插入成功。

避免在写入密集型工作负载中频繁出现压缩失败，例如[OLTP](glossary.html#glos_oltp)在应用程序中，MySQL 有时会在页面中保留一些空白空间（填充），以便修改日志更快地填满并重新压缩页面，同时仍有足够的空间来避免拆分它。每个页面中剩余的填充空间量随着系统跟踪页面拆分的频率而变化。在频繁写入压缩表的繁忙服务器上，您可以调整[`innodb_compression_failure_threshold_pct`](innodb-parameters.html#sysvar_innodb_compression_failure_threshold_pct)， 和[`innodb_compression_pad_pct_max`](innodb-parameters.html#sysvar_innodb_compression_pad_pct_max)配置选项来微调此机制。

一般来说，MySQL 要求 InnoDB 表中的每个 B 树页面至少可以容纳两条记录。对于压缩表，此要求已放宽。B-tree 节点的叶子页面（无论是主键还是二级索引）只需要容纳一条记录，但该记录必须以未压缩的形式适合每页修改日志。如果[`innodb_strict_mode`](innodb-parameters.html#sysvar_innodb_strict_mode)是`在`, MySQL 检查最大行大小[`创建表`](create-table.html)要么[`创建索引`](create-index.html).如果该行不适合，则会发出以下错误消息：`错误 HY000：行太大`.

如果您在创建表时[`innodb_strict_mode`](innodb-parameters.html#sysvar_innodb_strict_mode)是关闭的，并且随后`插入`要么`更新`语句尝试创建不适合压缩页面大小的索引条目，操作失败`错误 42000：行大小太大`.（此错误消息未命名记录太大的索引，也未提及索引记录的长度或该特定索引页上的最大记录大小。）要解决此问题，请使用以下命令重建表[`更改表`](alter-table.html)并选择更大的压缩页面大小（`KEY_BLOCK_SIZE`)，缩短任何列前缀索引，或完全禁用压缩`ROW_FORMAT=动态`要么`ROW_FORMAT=紧凑`.

[`innodb_strict_mode`](innodb-parameters.html#sysvar_innodb_strict_mode)不适用于通用表空间，也支持压缩表。通用表空间的表空间管理规则严格独立于[`innodb_strict_mode`](innodb-parameters.html#sysvar_innodb_strict_mode).有关详细信息，请参阅[第 13.1.21 节，“CREATE TABLESPACE 语句”](create-tablespace.html).

##### 压缩 BLOB、VARCHAR 和 TEXT 列

[](<>)[](<>)

在 InnoDB 表中，[`斑点`](blob.html),[`VARCHAR`](char.html)， 和[`文本`](blob.html)不属于主键的列可以存储在单独分配的[溢出页面](glossary.html#glos_overflow_page).我们将这些列称为[页外栏](glossary.html#glos_off_page_column).它们的值存储在溢出页面的单链表中。

对于创建的表`ROW_FORMAT=动态`要么`ROW_FORMAT=压缩`, 的值[`斑点`](blob.html),[`文本`](blob.html)， 要么[`VARCHAR`](char.html)列可以完全离页存储，具体取决于它们的长度和整行的长度。对于页外存储的列，聚集索引记录仅包含指向溢出页的 20 字节指针，每列一个。是否有任何列存储在页外取决于页面大小和行的总大小。当行太长而无法完全容纳在聚集索引的页面中时，MySQL 会选择最长的列进行页外存储，直到该行适合聚集索引页面为止。如上所述，如果一行在压缩页面上不适合自身，则会发生错误。

笔记

对于创建的表`ROW_FORMAT=动态`要么`ROW_FORMAT=压缩`,[`文本`](blob.html)和[`斑点`](blob.html)小于或等于 40 字节的列始终存储在行内。

使用的表`ROW_FORMAT=冗余`和`ROW_FORMAT=紧凑`存储前 768 个字节[`斑点`](blob.html),[`VARCHAR`](char.html)， 和[`文本`](blob.html)聚集索引记录中的列以及主键。768 字节前缀后跟一个 20 字节指针，指向包含其余列值的溢出页。

当一张桌子在`压缩`格式，写入溢出页的所有数据都“按原样”压缩；即 MySQL 对整个数据项应用 zlib 压缩算法。除了数据之外，压缩的溢出页面还包含未压缩的标题和尾部，其中包括页面校验和和到下一个溢出页面的链接等。因此，可以在更长的时间内获得非常显着的存储节省`斑点`,`文本`， 要么`VARCHAR`列，如果数据是高度可压缩的，这通常是文本数据的情况。图像数据，例如`JPEG`, 通常已经被压缩，因此存储在压缩表中并没有太多好处；双重压缩可能会浪费 CPU 周期而很少或根本没有节省空间。

溢出页面的大小与其他页面相同。即使列的总长度只有 8K 字节，包含 10 列存储在页外的行也会占用 10 个溢出页。在一个未压缩的表中，十个未压缩的溢出页占用 160K 字节。在一个 8K 页大小的压缩表中，它们只占用 80K 字节。因此，对于具有长列值的表，使用压缩表格式通常更有效。

为了[每表文件](glossary.html#glos_file_per_table)表空间，使用 16K 压缩页面大小可以降低存储和 I/O 成本[`斑点`](blob.html),[`VARCHAR`](char.html)， 要么[`文本`](blob.html)列，因为此类数据通常压缩得很好，因此可能需要更少的溢出页面，即使 B 树节点本身占用与未压缩形式一样多的页面。通用表空间不支持 16K 压缩页面大小（`KEY_BLOCK_SIZE`）。有关详细信息，请参阅[第 15.6.3.3 节，“通用表空间”](general-tablespaces.html).

##### 压缩和 InnoDB 缓冲池

[](<>)[](<>)

在一个压缩`InnoDB`表中，每个压缩页面（无论是 1K、2K、4K 还是 8K）对应于一个 16K 字节的未压缩页面（如果[`innodb_page_size`](innodb-parameters.html#sysvar_innodb_page_size)已设置）。为了访问一个页面中的数据，MySQL 从磁盘中读取压缩页面，如果它还没有在[缓冲池](glossary.html#glos_buffer_pool)，然后将页面解压缩为其原始形式。本节介绍如何`InnoDB`管理关于压缩表页的缓冲池。

为了最小化 I/O 并减少解压缩页面的需要，缓冲池有时会同时包含数据库页面的压缩和未压缩形式。为了为其他所需的数据库页面腾出空间，MySQL 可以[驱逐](glossary.html#glos_eviction)从缓冲池中取出一个未压缩的页面，同时将压缩页面留在内存中。或者，如果一个页面有一段时间没有被访问过，该页面的压缩形式可能会被写入磁盘，为其他数据腾出空间。因此，在任何给定时间，缓冲池都可能包含页面的压缩和未压缩形式，或者仅包含页面的压缩形式，或者两者都不包含。

MySQL 使用最近最少使用的 ([LRU](glossary.html#glos_lru)) 列表，因此[热的](glossary.html#glos_hot)（经常访问的）数据往往会留在内存中。当访问压缩表时，MySQL 使用自适应 LRU 算法来实现内存中压缩和未压缩页面的适当平衡。这种自适应算法对系统是否运行在一个[I/O 绑定](glossary.html#glos_io_bound)要么[CPU 密集型](glossary.html#glos_cpu_bound)方式。目标是避免在 CPU 繁忙时花费过多的处理时间来解压缩页面，并避免在 CPU 有空闲周期可用于解压缩压缩页面（可能已经在内存中）时执行过多的 I/O。当系统是 I/O-bound 时，该算法更喜欢驱逐一个页面的未压缩副本而不是两个副本，以便为其他磁盘页面腾出更多空间来驻留内存。当系统受 CPU 限制时，MySQL 更倾向于驱逐压缩和未压缩的页面，以便更多内存可用于“热”页面，并减少仅以压缩形式解压缩内存中数据的需要。

##### 压缩和 InnoDB 重做日志文件

[](<>)

在将压缩页面写入[数据文件](glossary.html#glos_data_files), MySQL 将页面的副本写入重做日志（如果自上次写入数据库以来已重新压缩）。这样做是为了确保重做日志可用于[崩溃恢复](glossary.html#glos_crash_recovery)，即使在不太可能的情况下`zlib`库已升级，并且该更改引入了压缩数据的兼容性问题。因此，一些规模的增加[日志文件](glossary.html#glos_log_file)，或者需要更频繁[检查站](glossary.html#glos_checkpoint), 在使用压缩时可以预期。日志文件大小或检查点频率的增加量取决于以需要重组和重新压缩的方式修改压缩页面的次数。

要在 file-per-table 表空间中创建压缩表，[`innodb_file_per_table`](innodb-parameters.html#sysvar_innodb_file_per_table)必须启用。不依赖于[`innodb_file_per_table`](innodb-parameters.html#sysvar_innodb_file_per_table)在通用表空间中创建压缩表时设置。有关详细信息，请参阅[第 15.6.3.3 节，“通用表空间”](general-tablespaces.html).
