14.9.1.5 InnoDBtable 压缩的工作方式

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

Compression Algorithms

某些 os 在文件系统级别实现压缩。文件通常分为固定大小的块,然后压缩为可变大小的块,这很容易导致碎片。每次修改块中的某些内容时,整个块都会重新压缩,然后再写入磁盘。这些属性使该压缩技术不适合在更新密集型数据库系统中使用。

MySQL 在著名的zlib library的帮助下实现了压缩,该zlib library实现了 LZ77 压缩算法。这种压缩算法在 CPU 利用率和数据大小减小方面都是成熟,可靠且有效的。该算法是“无损的”,因此始终可以从压缩形式重建原始未压缩数据。 LZ77 压缩通过查找在要压缩的数据内重复的数据序列来工作。数据中的值模式决定了压缩的程度,但是典型的用户数据通常压缩 50%或更多。

Note

在 MySQL 5.7.24 之前,InnoDB支持zlib库,版本最高为 1.2.3. 在 MySQL 5.7.24 和更高版本中,InnoDB支持zlib库,最高版本为 1.2.11.

与应用程序执行的压缩或某些其他数据库 Management 系统的压缩功能不同,InnoDB 压缩既适用于用户数据,也适用于索引。在许多情况下,索引可以构成数据库总大小的 40%到 50%或更多,因此这种差异非常明显。当压缩对于数据集工作得很好时,InnoDB 数据文件(file-per-tabletable 空间或general tablespace .ibd文件)的大小为未压缩大小的 25%至 50%或更小。取决于workload,这个较小的数据库又可以导致 I/O 减少和吞吐量增加,而就增加 CPU 使用率而言,成本却不高。您可以通过修改innodb_compression_level配置选项来调整压缩级别和 CPU 开销之间的平衡。

InnoDB 数据存储和压缩

InnoDBtable 中的所有用户数据都存储在包含B-tree索引(即clustered index)的页面中。在其他一些数据库系统中,这种类型的索引称为“索引组织 table”。索引节点中的每一行都包含(用户指定或系统生成的)primary key的值以及 table 的所有其他列。

InnoDBtable 中的Secondary indexes也是 B 树,包含两对值:索引键和指向聚集索引中行的指针。实际上,指针是 table 的主键的值,如果需要除索引键和主键以外的列,则该指针用于访问聚 Cluster 索引。次要索引记录必须始终适合单个 B 树页面。

B 树节点(群集索引和二级索引)的压缩与用于存储长VARCHARBLOBTEXT列的overflow pages压缩的处理方式不同,如以下各节所述。

B 树页面的压缩

由于 B 树页面经常更新,因此需要特殊对待。重要的是,最小化 B 树节点的分割次数,以及最小化解压缩和重新压缩其内容的需求。

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

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

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

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

通常,MySQL 要求 InnoDBtable 中的每个 B 树页面至少可以容纳两个记录。对于压缩 table,此要求已放宽。 B 树节点的叶子页(无论是主键还是辅助索引)仅需要容纳一个记录,但是该记录必须以未压缩的形式容纳在每页修改日志中。如果innodb_strict_modeON,则 MySQL 将在CREATE TABLECREATE INDEX期间检查最大行大小。如果该行不适合,则会发出以下错误消息:ERROR HY000: Too big row

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

innodb_strict_mode不适用于也支持压缩 table 的常规 table 空间。通用 table 空间的 table 空间 Management 规则严格独立于innodb_strict_mode实施。有关更多信息,请参见第 13.1.19 节“ CREATE TABLESPACE 语句”

压缩 BLOB,VARCHAR 和 TEXT 列

在 InnoDBtable 中,不属于主键的BLOBVARCHARTEXT列可以存储在单独分配的overflow pages上。我们将这些列称为off-page columns。它们的值存储在溢出页面的单链接列 table 中。

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

Note

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

在旧版 MySQL 中创建的 table 使用Antelope文件格式,该格式仅支持ROW_FORMAT=REDUNDANTROW_FORMAT=COMPACT。在这些格式中,MySQL 将BLOBVARCHARTEXT列的前 768 个字节与主键一起存储在聚集索引 Logging。 768 字节的前缀后跟一个 20 字节的指针,该指针指向包含其余列值的溢出页。

当 table 为COMPRESSED格式时,所有写入溢出页的数据均按“原样”压缩;也就是说,MySQL 将 zlib 压缩算法应用于整个数据项。除数据外,压缩的溢出页面还包含未压缩的头和尾,其中包括页面校验和以及到下一个溢出页面的链接等。因此,如果数据具有高度可压缩性(对于文本数据通常如此),则更长的BLOBTEXTVARCHAR列可以获得非常显着的存储节省。图像数据(例如JPEG)通常已经被压缩,因此从压缩 table 中存储不会带来太多好处。双重压缩会浪费 CPU 周期,几乎或根本不会节省空间。

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

对于file-per-tabletable 空间,使用 16K 压缩页大小可以减少BLOBVARCHARTEXT列的存储和 I/O 成本,因为此类数据通常压缩得很好,因此即使 B 树节点本身也需要较少的溢出页占用与未压缩形式一样多的页面。常规 table 空间不支持 16K 压缩页面大小(KEY_BLOCK_SIZE)。有关更多信息,请参见第 14.6.3.3 节“常规 table 空间”

压缩和 InnoDB 缓冲池

在压缩的InnoDBtable 中,每个压缩页(无论是 1K,2K,4K 还是 8K)都对应于一个 16K 字节的未压缩页(如果设置了innodb_page_size,则较小的页)。为了访问页面中的数据,MySQL 从磁盘读取压缩的页面(如果它不在buffer pool中),然后将页面解压缩为原始形式。本节描述InnoDB如何针对压缩 table 的页面 Management 缓冲池。

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

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

压缩和 InnoDB 重做日志文件

在将压缩的页面写入data file之前,MySQL 将页面的副本写入重做日志(如果自上次将其写入数据库以来已对其进行了重新压缩)。这样做是为了确保重做日志可用于crash recovery,即使在极少数情况下zlib库已升级并且更改带来了与压缩数据的兼容性问题也是如此。因此,使用压缩时,可以预期log files的大小会有所增加,或者需要更频繁的checkpoints。日志文件大小或检查点频率的增加量取决于以需要重组和重新压缩的方式修改压缩页面的次数。

压缩 table 需要Barracuda文件格式。要在每个 table 文件 table 空间中创建压缩 table,必须启用innodb_file_per_table并将innodb_file_format设置为Barracuda。在常规 table 空间中创建压缩 table 时,不依赖于innodb_file_format设置。有关更多信息,请参见第 14.6.3.3 节“常规 table 空间”MySQL 企业备份产品支持Barracuda文件格式。