Documentation Home
MySQL 8.4 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 39.8Mb
PDF (A4) - 39.9Mb
Man Pages (TGZ) - 257.9Kb
Man Pages (Zip) - 364.9Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 Reference Manual  /  ...  /  How Compression Works for InnoDB Tables

17.9.1.5 InnoDB 表中的压缩工作原理

本节描述了InnoDB表的压缩实现细节。以下信息可能对性能调整有帮助,但对于基本使用压缩不必要知道。

一些操作系统在文件系统级别实现了压缩。文件通常被分割成固定大小的块,然后将其压缩成可变大小的块,这容易导致碎片化。每当块内的某些内容被修改时,整个块都需要重新压缩,然后写回磁盘。这使得这种压缩技术不适用于更新频繁的数据库系统。

MySQL 使用知名的zlib库帮助实现压缩,该库实现了LZ77压缩算法。这是一种成熟、robust和高效的算法,既在CPU利用率方面又在数据大小减少方面都非常有效。该算法是无损的,即原始未压缩数据总是可以从压缩形式中重构出来。LZ77压缩算法通过在要压缩的数据中找到重复的数据序列来工作。您的数据中的值模式确定了它如何压缩,但通常用户数据可以压缩到50%或更高。

与应用程序或其他数据库管理系统的压缩功能不同,InnoDB 压缩同时适用于用户数据和索引。在许多情况下,索引可以占用总数据库大小的40-50%或更多,这个差异是非常重要的。当压缩对数据集工作良好时,InnoDB 数据文件(表空间或通用表空间.ibd 文件)的大小将是未压缩大小的25%-50%,甚至更小。根据工作负载,这个较小的数据库可以进一步减少I/O操作,并提高吞吐量,但这需要在CPU利用率方面增加一些成本。你可以通过修改innodb_compression_level配置选项来调整压缩级别和CPU开销的平衡。

InnoDB 表中的所有用户数据都存储在由B-树索引组成的页面中(-clustered index)。在其他一些数据库系统中,这种索引称为索引组织表。每个索引节点中的行包含了用户指定或系统生成的主键值和表中的所有其他列。

Secondary indexes 在 InnoDB 表中也是一种 B 树,包含键值对:索引键和指向聚簇索引的行。指针实际上是表的主键值,这个值用于访问聚簇索引,如果需要访问除索引键和主键外的列。Secondary index 记录总是必须在单个 B 树页面中。

B 树节点(包括聚簇索引和 secondary indexes)的压缩方式不同于用于存储长VARCHARBLOBTEXT列的溢出页面的压缩方式,详见以下部分。

由于频繁更新,B 树页面需要特殊处理。减少 B 树节点的拆分次数,以及减少其内容的解压和重新压缩是非常重要的。

MySQL 使用的一种技术是将一些系统信息在 B 树节点中保持未压缩的形式,从而 facilitiate certain in-place updates。例如,这允许行被 delete-marked 和删除,而无需执行任何压缩操作。

此外,MySQL 尝试避免更改索引页面时不必要的解压和重新压缩。在每个 B 树页面中,系统保持一个未压缩的修改日志来记录对该页面所做的更改。小记录的插入和更新可能会被写入到这个修改日志中,而不需要整个页面被完全重建。

当修改日志空间不足时,InnoDB 会解压页面、应用更改并重新压缩页面。如果重新压缩失败(即出现压缩失败),B-树节点将被分割,并且过程将重复直到更新或插入操作成功。

为了避免在写密集型工作负载中(例如OLTP应用程序)频繁出现压缩失败,MySQL 有时会在页面中留出一些空闲空间(填充),以便修改日志尽快填满,并在还有足够的空间时重新压缩页面。每个页面中的空闲空间量随着系统跟踪页面分割的频率而变化。在忙碌的服务器上,执行频繁写入压缩表的操作,您可以调整innodb_compression_failure_threshold_pctinnodb_compression_pad_pct_max配置选项以 fine-tune 这个机制。

一般来说,MySQL 需要每个 InnoDB 表的 B-树页面至少容纳两个记录。对于压缩表,这个要求已经放松了。B-树节点的叶子页面(无论是主键索引还是次要索引)只需要容纳一个记录,但是该记录必须在未压缩形式下能够 fit 在每页修改日志中。如果 innodb_strict_mode 设置为ON,MySQL 将在创建表或索引时检查行的最大大小。如果行不 fit,以下错误消息将被发出:ERROR HY000: Too big row

如果您在 innodb_strict_mode 设置为 OFF 时创建表,并且后续的 INSERTUPDATE 语句尝试创建一个不 fit 在压缩页面大小的索引记录,操作将失败并返回错误消息:ERROR 42000: Row size too large。(这个错误消息不会提到该记录在哪个索引中太大,也不会提到索引记录的长度或该特定索引页面上的最大记录大小。)要解决这个问题,可以使用 ALTER TABLE 重建表,选择更大的压缩页面大小(KEY_BLOCK_SIZE),简化任何列前缀索引,或者完全禁用压缩使用 ROW_FORMAT=DYNAMICROW_FORMAT=COMPACT

innodb_strict_模式 对于一般表空间无效,这些表空间还支持压缩表。一般表空间的管理规则独立于innodb_strict_模式严格执行。更多信息,请见第15.1.21节,“CREATE TABLESPACE 语句”

在 InnoDB 表中,BLOBVARCHARTEXT列,如果不是主键的一部分可能存储在单独分配的溢出页中。我们将这些列称为off-page 列。它们的值存储在溢出页的单链表中。

对于在ROW_FORMAT=DYNAMICROW__FORMAT=COMPRESSED中创建的表,BLOBTEXTVARCHAR列的值可能会完全存储在页面外,取决于它们的长度和整个行的长度。对于存储在页面外的列,聚簇索引记录只包含20字节的溢出页指针,一条记录一个。是否有列存储在页面外取决于页面大小和整个行的总大小。当行太长无法完全在聚簇索引页面上时,MySQL会选择最长的列进行页面存储直到行可以在聚簇索引页面上。如果一行不能单独在压缩页面上,则出现错误。

Note

对于在ROW_FORMAT=DYNAMICROW__FORMAT=COMPRESSED中创建的表,TEXTBLOB列,如果它们的长度小于或等于40字节,则总是存储在行内。

使用ROW_FORMAT=REDUNDANTROW_FORMAT=COMPACT的表格将BLOBVARCHARTEXT列的前768个字节存储在聚簇索引记录中,紧接着是20个字节的指针,指向包含剩余列值的溢出页。

当表格以COMPRESSED格式存在时,所有写入到溢出页中的数据都将被压缩as is;即MySQL对整个数据项应用zlib压缩算法。除了数据,压缩的溢出页还包含一个未压缩的头部和尾部,包括页面校验和下一个溢出页的链接等其他信息。因此,如果数据是高度可压缩的(通常情况下文本数据是这样),那么可以获得很大的存储空间节省。如果是图像数据,如JPEG,那么它通常已经被压缩,因此在压缩表格中不太可能获得太多的存储空间节省;双重压缩可能会浪费CPU cycles以换取少量或无空间节省。

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

对于file-per-表表空间,使用16K压缩页面大小可以减少BLOBVARCHARTEXT列的存储和I/O成本,因为这种数据通常可以很好地压缩,从而可能需要更少的溢出页面,即使B-树节点本身占用与未压缩形式相同的页面。一般表空间不支持16K压缩页面大小(KEY_BLOCK_SIZE)。更多信息,请见第17.6.3.3节,“General Tablespaces”

Compression and the InnoDB 缓冲池

在压缩的InnoDB表中,每个压缩页面(无论是1K、2K、4K还是8K)对应一个未压缩页面的16K字节(或更小的大小,如果设置了innodb_page_size)。要访问页面中的数据,MySQL从磁盘读取压缩页面,如果它不在缓冲池中,然后将页面解压到其原始形式。这一节描述了InnoDB如何管理缓冲池,以便对压缩表的页面进行管理。

为了减少I/O操作和减少需要解压页面的需求,在某些情况下,缓冲池中同时包含压缩和未压缩数据库页面的形式。为了腾出空间给其他需要的数据库页面,MySQL可以驱逐缓冲池中的未压缩页面,同时保留压缩页面在内存中。或者,如果页面很长时间没有被访问过,压缩页面可能会被写入磁盘,以释放空间给其他数据。因此,在任何给定的时间点,缓冲池可能同时包含压缩和未压缩页面的形式,也可能只包含压缩页面的形式,也可能不包含任何一页的形式。

MySQL 使用最近最少使用(LRU)列表来跟踪哪些页面需要保留在内存中,哪些需要淘汰,以便频繁访问的数据()更容易保持在内存中。对压缩表进行访问时,MySQL 使用一个自适应 LRU 算法来实现内存中的压缩和未压缩页面的合理平衡。这一算法对系统是否处于 I/O- bound(I/O- bound)或 CPU- bound(CPU- bound)状态非常敏感。目标是避免在 CPU忙碌时花太多时间来解压页面,并且避免在 CPU有空闲 cycles 时做过多的 I/O 操作,以便可以用于解压已经在内存中的压缩页面(可能已经在内存中)。当系统处于 I/O- bound 状态时,算法更喜欢淘汰未压缩页面,而不是两个副本,以便为其他磁盘页面腾出更多的内存空间。当系统处于 CPU- bound 状态时,MySQL 更喜欢淘汰两个副本,以便可以将更多的内存用于““热”” 页面,并减少内存中只包含压缩形式的数据需要解压的需求。

Compression and the InnoDB 重做日志 Files

在将压缩页面写入数据文件之前,MySQL 将该页面的副本写入redo日志(如果自上次写入数据库以来已经被重新压缩)。这做的是确保redo日志在崩溃恢复中仍然可用,即使在zlib库升级时出现了与压缩数据不兼容的问题。因此,在使用压缩时,可以预期日志文件的大小增加或checkpoint的频率增加。日志文件大小或checkpoint频率的增加幅度取决于被重新组织和重新压缩的压缩页面数量。

要在文件表空间中创建一个压缩表,需要启用innodb_file_per_table。在一般表空间中创建压缩表时,不依赖于innodb_file_per_table设置。更多信息,请见第17.6.3.3节,“一般表空间”