Documentation Home
MySQL 8.0 参考手册  / 第 15 章 InnoDB 存储引擎  / 15.9 InnoDB 表和页压缩  /  14.9.5 InnoDB 表的压缩工作原理

14.9.5 InnoDB 表的压缩工作原理

本节描述有关InnoDB 表压缩的 一些内部实现细节 。此处提供的信息可能有助于调整性能,但对于压缩的基本使用而言不是必需的。

压缩算法

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

MySQL 借助著名的 zlib 库实现压缩,该库实现了 LZ77 压缩算法。这种压缩算法在 CPU 利用率和数据大小缩减方面成熟、稳健且高效。该算法是 无损的,因此原始的未压缩数据总是可以从压缩形式中重建出来。LZ77 压缩通过查找在要压缩的数据中重复的数据序列来工作。数据中值的模式决定了它的压缩程度,但典型的用户数据通常会压缩 50% 或更多。

笔记

在 MySQL 5.6.42 之前,InnoDB支持 zlib1.2.3 版本以下的库。在 MySQL 5.6.42 及更高版本中,InnoDB支持 zlib版本最高为 1.2.11 的库。

与应用程序执行的压缩或某些其他数据库管理系统的压缩功能不同,InnoDB 压缩适用于用户数据和索引。在许多情况下,索引可能占数据库总大小的 40-50% 或更多,因此这种差异很明显。当数据集的压缩效果很好时,InnoDB 数据文件(.ibd文件)的大小是未压缩大小的 25% 到 50% 或可能更小。根据 工作负载的不同,这个较小的数据库反过来会导致 I/O 减少和吞吐量增加,而在增加 CPU 利用率方面的成本适中。您可以通过修改压缩级别和 CPU 开销之间的平衡来调整 innodb_compression_level 配置选项。

InnoDB 数据存储和压缩

InnoDB 表中的所有用户数据都存储在包含 B 树索引( 聚集索引)的页面中。在其他一些数据库系统中,这种类型的索引被称为 索引组织表。索引节点中的每一行都包含(用户指定的或系统生成的) 主键的值以及表的所有其他列。

InnoDB 表中的二级索引也是 B 树,包含成对的值:索引键和指向聚集索引中一行的指针。该指针实际上是表的主键的值,如果需要索引键和主键以外的列,则用于访问聚簇索引。二级索引记录必须始终适合单个 B 树页面。

B 树节点(包括聚集索引和二级索引)的压缩处理方式与 用于存储 long 、或 列的溢出页压缩的处理方式不同,如以下部分所述。 VARCHARBLOBTEXT

B 树页面的压缩

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

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

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

当修改日志的空间用完时,InnoDB 解压缩页面,应用更改并重新压缩页面。如果重新压缩失败(这种情况称为 压缩失败),B 树节点将被拆分并重复该过程,直到更新或插入成功。

为了避免写入密集型工作负载(例如OLTP 应用程序)中频繁压缩失败,MySQL 有时会在页面中保留一些空白空间(填充),以便修改日志更快填满,并在仍有足够空间的情况下重新压缩页面避免分裂它。每个页面中剩余的填充空间量随着系统跟踪页面拆分的频率而变化。在频繁写入压缩表的繁忙服务器上,您可以调整 innodb_compression_failure_threshold_pctinnodb_compression_pad_pct_max 配置选项来微调此机制。

通常,MySQL 要求 InnoDB 表中的每个 B 树页至少可以容纳两条记录。对于压缩表,此要求已放宽。B 树节点的叶页(无论是主键还是二级索引)只需要容纳一条记录,但该记录必须以未压缩的形式适合每页修改日志。如果 innodb_strict_mode是 ,MySQL 在或 ON期间检查最大行大小 。如果该行不适合,将发出以下错误消息:。 CREATE TABLECREATE INDEXERROR HY000: Too big row

如果您在 OFF 时创建表 innodb_strict_mode,并且后续INSERTorUPDATE 语句尝试创建不适合压缩页面大小的索引条目,则操作失败并显示 ERROR 42000: Row size too large。(此错误消息未命名记录过大的索引,也未提及索引记录的长度或该特定索引页上的最大记录大小。)要解决此问题,请重建表ALTER TABLE 并选择一个更大的压缩页面大小 ( ),缩短任何列前缀索引,或使用或 KEY_BLOCK_SIZE完全禁用压缩 。 ROW_FORMAT=DYNAMICROW_FORMAT=COMPACT

压缩 BLOB、VARCHAR 和 TEXT 列

在 InnoDB 表中,BLOB不 属于主键VARCHARTEXT列可能存储在单独分配的 溢出页上。我们将这些列称为离页列。它们的值存储在溢出页面的单链表中。

对于在ROW_FORMAT=DYNAMIC或 中创建的表, 、 或 列ROW_FORMAT=COMPRESSED的值 可能会完全页外存储,具体取决于它们的长度和整行的长度。对于页外存储的列,聚集索引记录仅包含指向溢出页的 20 字节指针,每列一个。是否有任何列存储在页外取决于页大小和行的总大小。当行太长无法完全容纳在聚簇索引的页中时,MySQL 选择最长的列进行页外存储,直到该行适合聚簇索引页。如上所述,如果一行本身不适合压缩页,则会发生错误。 BLOBTEXTVARCHAR

笔记

对于在ROW_FORMAT=DYNAMICROW_FORMAT=COMPRESSED中 创建的表,小于或等于 40 字节的列始终以内联方式存储 TEXTBLOB

在旧版本的 MySQL 中创建的表使用 Antelope文件格式,该格式仅支持ROW_FORMAT=REDUNDANTROW_FORMAT=COMPACT. BLOB在这些格式中,MySQL 将、 VARCHAR和 列的前 768 个字节TEXT与主键一起存储在聚簇索引记录中。768 字节前缀后跟一个 20 字节指针,指向包含其余列值的溢出页。

当表处于COMPRESSED格式化状态时,所有写入溢出页的数据都将按原样压缩;即MySQL对整个数据项应用了zlib压缩算法。除数据外,压缩的溢出页面还包含未压缩的页眉和尾部,包括页面校验和和指向下一个溢出页面的链接等。因此,如果数据是高度可压缩的(文本数据通常就是这种情况),则对于更长BLOBTEXT、 或 列,可以获得非常显着的存储节省 。VARCHAR图像数据,例如JPEG,通常已经被压缩,因此存储在压缩表中不会带来太多好处;双重压缩会浪费 CPU 周期,而节省的空间很少或根本没有。

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

使用 16K 压缩页面大小可以减少、 或 列的存储和 I/O 成本BLOB, 因为此类数据通常压缩得很好,因此可能需要更少的溢出页面,即使 B 树节点本身占用的页面与未压缩的形式。 VARCHARTEXT

压缩和 InnoDB 缓冲池

在压缩的 InnoDB 表中,每个压缩页面(无论是 1K、2K、4K 还是 8K)都对应一个 16K 字节的未压缩页面(如果 innodb_page_size设置了,则为更小的大小)。为了访问页面中的数据,如果 缓冲池中不存在压缩页面,MySQL 会从磁盘读取压缩页面,然后将页面解压缩为其原始形式。本节介绍 InnoDB 如何管理压缩表页面的缓冲池。

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

MySQL 使用最近最少使用 ( LRU ) 列表 来跟踪哪些页面要保留在内存中,哪些要逐出,以便 (经常访问的)数据倾向于保留在内存中。当访问压缩表时,MySQL 使用自适应 LRU 算法在内存中实现压缩和未压缩页面的适当平衡。这种自适应算法对系统是运行在I/O-bound还是 CPU-bound很敏感方式。目标是避免在 CPU 繁忙时花费太多处理时间来解压缩页面,并避免在 CPU 有空闲周期可用于解压缩压缩页面(可能已经在内存中)时执行过多的 I/O。当系统受 I/O 限制时,该算法更愿意逐出页面的未压缩副本而不是两个副本,以便为其他磁盘页面腾出更多空间驻留在内存中。当系统受 CPU 限制时,MySQL 更愿意同时驱逐压缩和未压缩页面,以便更多内存可用于页面,并减少仅以压缩形式在内存中解压缩数据的需要。

压缩和 InnoDB 重做日志文件

在将压缩页面写入 数据文件之前,MySQL 将该页面的副本写入重做日志(如果自上次写入数据库以来已重新压缩)。这样做是为了确保重做日志可用于 崩溃恢复,即使在不太可能发生的情况下,zlib库已升级并且该更改会引入与压缩数据的兼容性问题。因此,日志文件的大小有所增加 ,或者需要更频繁的检查点, 在使用压缩时可以预期。日志文件大小或检查点频率的增加量取决于以需要重组和重新压缩的方式修改压缩页面的次数。

与 MySQL 5.1 及更早版本相比,压缩表对重做日志和每表表空间使用不同的文件格式。MySQL Enterprise Backup产品支持用于压缩 InnoDB 表的 最新 Barracuda文件格式。