本节描述有关InnoDB 表压缩的 一些内部实现细节。此处提供的信息可能有助于调整性能,但对于压缩的基本使用而言不是必需的。
压缩算法
一些操作系统在文件系统级别实施压缩。文件通常被分成固定大小的块,这些块被压缩成可变大小的块,这很容易导致碎片化。每次修改块内的内容时,整个块都会在写入磁盘之前重新压缩。这些特性使这种压缩技术不适合在更新密集型数据库系统中使用。
MySQL 借助著名的 zlib 库实现压缩,该库实现了 LZ77 压缩算法。这种压缩算法在 CPU 利用率和数据大小缩减方面成熟、稳健且高效。该算法是 “无损”的,因此原始的未压缩数据总是可以从压缩形式中重建出来。LZ77 压缩通过查找在要压缩的数据中重复的数据序列来工作。数据中值的模式决定了它的压缩程度,但典型的用户数据通常会压缩 50% 或更多。
InnoDB
支持zlib
1.2.11 版本的库,这是与 MySQL 8.0 捆绑在一起的版本。
与应用程序执行的压缩或某些其他数据库管理系统的压缩功能不同,InnoDB 压缩适用于用户数据和索引。在许多情况下,索引可能占数据库总大小的 40-50% 或更多,因此这种差异很明显。当数据集的压缩效果很好时,InnoDB 数据文件(
每个表的文件表
空间或一般表空间 .ibd
文件)的大小是未压缩大小的 25% 到 50% 或可能更小。视
工作量而定,这个较小的数据库反过来会导致 I/O 的减少和吞吐量的增加,而在增加 CPU 利用率方面的代价是适度的。innodb_compression_level
您可以通过修改配置选项
来调整压缩级别和 CPU 开销之间的平衡
。
InnoDB 数据存储和压缩
InnoDB 表中的所有用户数据都存储在包含 B 树索引( 聚集索引)的页面中。在其他一些数据库系统中,这种类型的索引被称为 “索引组织表”。索引节点中的每一行都包含(用户指定的或系统生成的) 主键的值以及表的所有其他列。
InnoDB 表中的二级索引也是 B 树,包含成对的值:索引键和指向聚集索引中一行的指针。该指针实际上是表的主键的值,如果需要索引键和主键以外的列,则用于访问聚簇索引。二级索引记录必须始终适合单个 B 树页面。
B 树节点(包括聚集索引和二级索引)的压缩处理方式与
用于存储 long 、或列的溢出页压缩的处理方式不同,如以下部分所述。
VARCHAR
BLOB
TEXT
B 树页面的压缩
因为它们经常更新,所以 B 树页面需要特殊处理。重要的是尽量减少 B 树节点的分裂次数,以及尽量减少解压缩和重新压缩其内容的需要。
MySQL 使用的一种技术是以未压缩的形式在 B 树节点中维护一些系统信息,从而促进某些就地更新。例如,这允许在不进行任何压缩操作的情况下对行进行删除标记和删除。
此外,MySQL 会尝试在索引页更改时避免不必要的解压缩和重新压缩。在每个 B-tree 页面中,系统保留一个未压缩的 “修改日志”来记录对页面所做的更改。可以将小记录的更新和插入写入此修改日志,而无需完全重建整个页面。
当修改日志的空间用完时,InnoDB 解压缩页面,应用更改并重新压缩页面。如果重新压缩失败(这种情况称为 压缩失败),B 树节点将被拆分并重复该过程,直到更新或插入成功。
为了避免写入密集型工作负载(例如OLTP
应用程序)中频繁压缩失败,MySQL 有时会在页面中保留一些空白空间(填充),以便修改日志更快填满,并在仍有足够空间的情况下重新压缩页面避免分裂它。每个页面中剩余的填充空间量随着系统跟踪页面拆分的频率而变化。在频繁写入压缩表的繁忙服务器上,您可以调整
innodb_compression_failure_threshold_pct
和
innodb_compression_pad_pct_max
配置选项来微调此机制。
通常,MySQL 要求 InnoDB 表中的每个 B 树页至少可以容纳两条记录。对于压缩表,此要求已放宽。B 树节点的叶页(无论是主键还是二级索引)只需要容纳一条记录,但该记录必须以未压缩的形式适合每页修改日志。如果
innodb_strict_mode
是
,MySQL 在或
ON
期间检查最大行大小
。如果该行不适合,将发出以下错误消息:。
CREATE TABLE
CREATE INDEX
ERROR
HY000: Too big row
如果您在 OFF 时创建表
innodb_strict_mode
,并且后续INSERT
or
UPDATE
语句尝试创建不适合压缩页面大小的索引条目,则操作失败并显示ERROR 42000: Row size too
large
。(此错误消息未命名记录过大的索引,也未提及索引记录的长度或该特定索引页上的最大记录大小。)要解决此问题,请重建表
ALTER TABLE
并选择一个更大的压缩页面大小 ( ),缩短任何列前缀索引,或使用或
KEY_BLOCK_SIZE
完全禁用压缩。
ROW_FORMAT=DYNAMIC
ROW_FORMAT=COMPACT
innodb_strict_mode
不适用于通用表空间,它也支持压缩表。通用表空间的表空间管理规则是独立于
innodb_strict_mode
. 有关详细信息,请参阅第 13.1.21 节,“CREATE TABLESPACE 语句”。
压缩 BLOB、VARCHAR 和 TEXT 列
在 InnoDB 表中,BLOB
不
属于主键VARCHAR
的
TEXT
列可能存储在单独分配的
溢出页上。我们将这些列称为
离页列。它们的值存储在溢出页面的单链表中。
对于在ROW_FORMAT=DYNAMIC
或
中创建的表,
、 或
列ROW_FORMAT=COMPRESSED
的值
可能会完全页外存储,具体取决于它们的长度和整行的长度。对于页外存储的列,聚集索引记录仅包含指向溢出页的 20 字节指针,每列一个。是否有任何列存储在页外取决于页大小和行的总大小。当行太长无法完全容纳在聚簇索引的页中时,MySQL 选择最长的列进行页外存储,直到该行适合聚簇索引页。如上所述,如果一行本身不适合压缩页,则会发生错误。
BLOB
TEXT
VARCHAR
使用ROW_FORMAT=REDUNDANT
和
存储聚簇索引记录中、
和
列ROW_FORMAT=COMPACT
的前 768 个字节以及主键的表。768 字节前缀后跟一个 20 字节指针,指向包含其余列值的溢出页。
BLOB
VARCHAR
TEXT
当表处于COMPRESSED
格式化状态时,所有写入溢出页的数据都将“按原样”压缩;即MySQL对整个数据项应用了zlib压缩算法。除数据外,压缩的溢出页面还包含未压缩的页眉和尾部,包括页面校验和和指向下一个溢出页面的链接等。因此,如果数据是高度可压缩的(文本数据通常就是这种情况),则对于更长BLOB
的TEXT
、 或
列,可以获得非常显着的存储节省
。VARCHAR
图像数据,例如JPEG
,通常已经被压缩,因此存储在压缩表中不会带来太多好处;双重压缩会浪费 CPU 周期,而节省的空间很少或根本没有。
溢出页面与其他页面的大小相同。包含十列存储在页外的一行占用十个溢出页,即使列的总长度只有 8K 字节。在一个未压缩的表中,10 个未压缩的溢出页占用 160K 字节。在一个 8K 页面大小的压缩表中,它们只占用 80K 字节。因此,对具有长列值的表使用压缩表格式通常更有效。
对于file-per-table 表
空间,使用 16K 压缩页面大小可以减少、 或
列的存储和 I/O 成本BLOB
,
因为此类数据通常压缩得很好,因此可能需要更少的溢出页面,即使 B 树节点本身占用与未压缩形式一样多的页面。一般表空间不支持 16K 压缩页面大小 ( )。有关详细信息,请参阅
第 15.6.3.3 节,“通用表空间”。
VARCHAR
TEXT
KEY_BLOCK_SIZE
压缩和 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
库已升级并且该更改会引入与压缩数据的兼容性问题。因此,日志文件的大小有所增加
,或者需要更频繁的
检查点, 在使用压缩时可以预期。日志文件大小或检查点频率的增加量取决于以需要重组和重新压缩的方式修改压缩页面的次数。
要在 file-per-table 表空间中创建压缩表,
innodb_file_per_table
必须启用。innodb_file_per_table
在通用表空间中创建压缩表时不依赖于
设置。有关详细信息,请参阅第 15.6.3.3 节,“通用表空间”。