MySQL · 引擎特性 · InnoDB 大字段压缩

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介:

前言

当用户的数据量比较大时,通常需要对数据进行压缩,以减少磁盘占用。InnoDB目前有两种方式来实现这一目的。

第一种是传统的数据压缩,通过指定row_format及key_block_size,能够将用户表压缩到指定的page size并进行存储,默认使用zlib。这种压缩方式使用比较简单,但也是诟病较多的, 代码陈旧,相关代码基本上几个大版本都没发生过变化,一些优化点还是从facebook移植过来的(集中在在5.6版本中, 不过现在fb已经放弃优化InnoDB压缩了,转而聚集在自家压缩更好的myrock上)。InnoDB压缩表的性能瓶颈明显,尤其是在压缩page到指定size失败时触发索引分裂。

第二种是MySQL5.7引入的所谓transparent compression,通过文件系统punch hole和sparse file特性来实现的。具体的就是在将数据页进行压缩后,将留白的地方进行打洞,从而实现数据压缩的目的。这个实现的好处就是代码逻辑简单,整个feature的实现基本上没加多少代码,无需指定key_block_size(但依然需要根据文件系统block size对齐),并且也能更方便的支持多种压缩算法。但缺点也明显,例如可能会产生大量的文件碎片,底层的文件管理可能更复杂;也无法降低buffer pool的占用(传统的压缩方式可以只在buffer pool保存压缩页)

另外还有一种方式是通过MySQL函数compress/decompress,由应用端来决定存入的数据是否压缩,并控制解压操作。但这种方式不够灵活,需要应用来修改代码。

在AliSQL中我们提供了一种新的列压缩方式,用户在建表时可以将列属性column_format指定为compressed,那么服务器就会在存入/取出这个列的数据时,自动对其进行压缩和解压动作。这个方案不仅降低了磁盘数据大小,而且也能最大程度的保证性能,例如在查询不涉及到压缩列时无需执行解压动作。该特性尤其适用于诸如blob或者text这样的大列。

Percona Server也基于该补丁进行了功能扩展和优化。社区用户现在可以同时从AliSQL及Percona Server中获得该特性。

本文主要简单介绍下AliSQL如何实现的该特性,以及Percona的实现方案。

AliSQL实现

使用该特性非常简单,可以在建表时指定列属性,或者在ALTER TABLE来修改列属性。

mysql> CREATE TABLE t1 (a INT PRIMARY KEY,  b blob);
Query OK, 0 rows affected (0.00 sec)

mysql> ALTER TABLE t1 MODIFY COLUMN b BLOB COLUMN_FORMAT COMPRESSED;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `a` int(11) NOT NULL,
  `b` blob /*!50616 COLUMN_FORMAT COMPRESSED */,
  PRIMARY KEY (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
AI 代码解读

目前仅支持对blob/text/varchar/varbinary这几种类型进行压缩,如果在其他类型列上定义compressed属性,会抛出一个warning,并忽略列属性:

mysql> CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY, b INT COLUMN_FORMAT COMPRESSED);
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> SHOW WARNINGS;
+---------+------+------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                  |
+---------+------+------------------------------------------------------------------------------------------+
| Warning | 3002 | Can not define column 'b' in compressed format, silently change column_format to default |
+---------+------+------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
AI 代码解读

也不支持在压缩列上创建二级索引,因为压缩后的数据可能已经不具备顺序性,在其上创建索引没有意义,一个错误码会被抛出:

mysql> CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY, b BLOB COLUMN_FORMAT COMPRESSED, KEY (b(20)));
ERROR 3001 (HY000): Compressed BLOB/TEXT/VARCHAR/VARBINARY column 'b' used in key list is not allowed
AI 代码解读

由于大部分用户的引擎还是InnoDB,因此目前该特性仅支持InnoDB表(其实真实原因是笔者在写这个补丁时只对InnoDB比较了解.....),未来不排除这个特性实现到server层,这样就可以做到和引擎无关了。

代码的实现也比较简单,分为两部分

压缩

在InnoDB接受到行数据并进行任何处理之前,先将对应的列数据进行压缩.

入口函数:row_compress_column

压缩后的数据包含如下部分:

1. 一个字节的header:
- COLUMN_COMPRESS_FLAG (1bit), 数据是否进行过压缩
- COLUMN_COMPRESS_DATA_LEN(2bits), 原始列的长度
- COLUMN_COMPRESS_ALG(3bits), 压缩算法,目前值为0,表示只支持zlib
- COLUMN_COMPRESS_WRAP(1bit), 标示zlib是否计算了adler32值
- 保留1个bit

2. 数据压缩前的长度,占用的字节数存储在COLUMN_COMPRESS_DATA_LEN

3. 压缩后的数据
AI 代码解读

如果发现压缩后的数据比原始数据还大,则放弃压缩,但会额外浪费1个字节来进行标识

我们提供了一些参数来对压缩进行控制,包括

  1. innodb_rds_column_compression_level: zlib的压缩级别
  2. innodb_rds_column_zip_mem_use_heap: 压缩过程中的内存分配/释放的回调函数,是使用InnoDB自带的还是系统自带的
  3. innodb_rds_column_zip_threshold: 当数据长度超过这么大时,才去进行压缩; 这个参数需要根据数据特点来进行调整,否则如果对很小的字段进行压缩,没什么效果不说,反而还浪费cpu.
  4. innodb_rds_column_zlib_strategy:使用的zlib压缩策略:
  5. innodb_rds_column_zlib_wrap: 是否在压缩/解压时进行adler32校验

解压

在从InnoDB取到一条数据并返回到server层之前,对列进行解压

入口函数: row_decompress_column

解压也比较简单,首先根据Header中的信息判断是否进行了压缩;然后再读出原始数据的长度;找到压缩数据的起始位置并进行解压后,跟原始长度进行校验。

全局Status变量来监控压缩和解压的次数:

mysql> show status like '%column%compress%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Innodb_column_compressed   | 0     |
| Innodb_column_decompressed | 0     |
+----------------------------+-------+
2 rows in set (0.00 sec)
AI 代码解读

完整的补丁见commit

Percona实现

Percona Server的列压缩实现来自Pinterest的贡献,Pinterest以AliSQL的列压缩补丁作为基础,做了进一步的改进。他们写了一篇博客描述了整个开发过程,感兴趣的可以点开看看。

为了实现更好的压缩比,Percona 实现了一个称为 "predefined dictionary", 实际上这是引用了新版本的zlib的一个特性。在压缩初始化后(deflateInit2),可以去设置一个预定义的数据词典.

参阅函数row_compress_column

        err = deflateInit2(&c_stream, srv_compressed_columns_zip_level,
                Z_DEFLATED, window_bits, MAX_MEM_LEVEL,
                srv_compressed_columns_zlib_strategy);
        ut_a(err == Z_OK);

        if (dict_data != 0 && dict_data_len != 0) {
                err = deflateSetDictionary(&c_stream, dict_data,
                        dict_data_len);
                ut_a(err == Z_OK);
        }
AI 代码解读

Percona利用这个特性,并增加了一系列的接口来管理预定义词典。每个压缩列都可以通过显式的命名指向一个词典。

系统表

增加了一个新的系统表SYS_ZIP_DICT, 用于存储词典数据, 定义如下:

    CREATE TABLE SYS_ZIP_DICT(
      ID INT UNSIGNED NOT NULL,
      NAME CHAR(64) NOT NULL,
      DATA BLOB NOT NULL
    );
    
    CREATE UNIQUE CLUSTERED INDEX SYS_ZIP_DICT_ID
      ON SYS_ZIP_DICT (ID);
    CREATE UNIQUE INDEX SYS_ZIP_DICT_NAME
      ON SYS_ZIP_DICT (NAME);
      
    你可以从information_schema.xtradb_zip_dict获得字段信息
    
AI 代码解读

系统表SYS_ZIP_DICT_COLS,用于存储哪些使用预定义压缩词典的列信息,定义如下:

    CREATE TABLE SYS_ZIP_DICT_COLS(
      TABLE_ID INT UNSIGNED NOT NULL,
      COLUMN_POS INT UNSIGNED NOT NULL,
      DICT_ID INT UNSIGNED NOT NULL
    );
    
    CREATE UNIQUE CLUSTERED INDEX SYS_ZIP_DICT_COLS_COMPOSITE
      ON SYS_ZIP_DICT_COLS (TABLE_ID, COLUMN_POS);
    
    -- 建立在该表之上的视图:information_schema.xtradb_zip_dict_cols
    
AI 代码解读

创建词典

语法: CREATE COMPRESSION_DICTIONARY <dict>(...)

例如: 

mysql> CREATE COMPRESSION_DICTIONARY dt1('abcd');
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM INFORMATION_SCHEMA.XTRADB_ZIP_DICT;
+----+------+----------+
| id | name | zip_dict |
+----+------+----------+
|  1 | dt1  | abcd     |
+----+------+----------+
1 row in set (0.00 sec)

入口函数: innobase_create_zip_dict
AI 代码解读

使用词典

mysql> CREATE TABLE t1 (a INT PRIMARY KEY, b BLOB COLUMN_FORMAT COMPRESSED WITH COMPRESSION_DICTIONARY dt1);
Query OK, 0 rows affected (0.01 sec)

mysql> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `a` int(11) NOT NULL,
  `b` blob /*!50633 COLUMN_FORMAT COMPRESSED WITH COMPRESSION_DICTIONARY `dt1` */,
  PRIMARY KEY (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

mysql> SELECT * FROM INFORMATION_SCHEMA.XTRADB_ZIP_DICT_COLS;
+----------+------------+---------+
| table_id | column_pos | dict_id |
+----------+------------+---------+
|       22 |          1 |       1 |
+----------+------------+---------+
1 row in set (0.00 sec)

可以看到该列使用的预定义词典序号为1,对应dt1

当表上有引用的词典时,在打开表时就要从系统表中去进行关联(ha_innobase::update_field_defs_with_zip_dict_info)
AI 代码解读

删除词典

语法: DROP COMPRESSION_DICTIONARY <dict>

# 很显然,当有列引用到这个词典时,是不可以删除的

mysql> DROP COMPRESSION_DICTIONARY dt1;
ERROR 1894 (HY000): Compression dictionary 'dt1' is in use
mysql> ALTER TABLE t1 MODIFY COLUMN b BLOB COLUMN_FORMAT COMPRESSED;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> DROP COMPRESSION_DICTIONARY dt1;
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT * FROM INFORMATION_SCHEMA.XTRADB_ZIP_DICT_COLS;
Empty set (0.00 sec)

mysql> SELECT * FROM INFORMATION_SCHEMA.XTRADB_ZIP_DICT;
Empty set (0.00 sec

入口函数:innobase_drop_zip_dict
AI 代码解读

参考文档:

Percona Column compression 文档

How to find a good/optimal dictionary for zlib

代码实现

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
打赏
0
0
0
1
10011
分享
相关文章
MySQL底层概述—2.InnoDB磁盘结构
InnoDB磁盘结构主要包括表空间(Tablespaces)、数据字典(Data Dictionary)、双写缓冲区(Double Write Buffer)、重做日志(redo log)和撤销日志(undo log)。其中,表空间分为系统、独立、通用、Undo及临时表空间,分别用于存储不同类型的数据。数据字典从MySQL 8.0起不再依赖.frm文件,转而使用InnoDB引擎存储,支持事务原子性DDL操作。
271 100
MySQL底层概述—2.InnoDB磁盘结构
MySQL底层概述—1.InnoDB内存结构
本文介绍了InnoDB引擎的关键组件和机制,包括引擎架构、Buffer Pool、Page管理机制、Change Buffer、Log Buffer及Adaptive Hash Index。
293 97
MySQL底层概述—1.InnoDB内存结构
MySQL底层概述—10.InnoDB锁机制
本文介绍了:锁概述、锁分类、全局锁实战、表级锁(偏读)实战、行级锁升级表级锁实战、间隙锁实战、临键锁实战、幻读演示和解决、行级锁(偏写)优化建议、乐观锁实战、行锁原理分析、死锁与解决方案
150 24
MySQL底层概述—10.InnoDB锁机制
MySQL底层概述—5.InnoDB参数优化
本文介绍了MySQL数据库中与内存、日志和IO线程相关的参数优化,旨在提升数据库性能。主要内容包括: 1. 内存相关参数优化:缓冲池内存大小配置、配置多个Buffer Pool实例、Chunk大小配置、InnoDB缓存性能评估、Page管理相关参数、Change Buffer相关参数优化。 2. 日志相关参数优化:日志缓冲区配置、日志文件参数优化。 3. IO线程相关参数优化: 查询缓存参数、脏页刷盘参数、LRU链表参数、脏页刷盘相关参数。
117 12
MySQL底层概述—5.InnoDB参数优化
MySQL底层概述—4.InnoDB数据文件
本文介绍了InnoDB表空间文件结构及其组成部分,包括表空间、段、区、页和行。表空间是最高逻辑层,包含多个段;段由若干个区组成,每个区包含64个连续的页,页用于存储多条行记录。文章还详细解析了Page结构,分为通用部分(文件头与文件尾)、数据记录部分和页目录部分。此外,文中探讨了行记录格式,包括四种行格式(Redundant、Compact、Dynamic和Compressed),重点介绍了Compact行记录格式及其溢出机制。最后,文章解释了不同行格式的特点及应用场景,帮助理解InnoDB存储引擎的工作原理。
MySQL底层概述—4.InnoDB数据文件
MySQL底层概述—3.InnoDB线程模型
InnoDB存储引擎采用多线程模型,包含多个后台线程以处理不同任务。主要线程包括:IO Thread负责读写数据页和日志;Purge Thread回收已提交事务的undo日志;Page Cleaner Thread刷新脏页并清理redo日志;Master Thread调度其他线程,定时刷新脏页、回收undo日志、写入redo日志和合并写缓冲。各线程协同工作,确保数据一致性和高效性能。
MySQL底层概述—3.InnoDB线程模型
MySQL原理简介—2.InnoDB架构原理和执行流程
本文介绍了MySQL中更新语句的执行流程及其背后的机制,主要包括: 1. **更新语句的执行流程**:从SQL解析到执行器调用InnoDB存储引擎接口。 2. **Buffer Pool缓冲池**:缓存磁盘数据,减少磁盘I/O。 3. **Undo日志**:记录更新前的数据,支持事务回滚。 4. **Redo日志**:确保事务持久性,防止宕机导致的数据丢失。 5. **Binlog日志**:记录逻辑操作,用于数据恢复和主从复制。 6. **事务提交机制**:包括redo日志和binlog日志的刷盘策略,确保数据一致性。 7. **后台IO线程**:将内存中的脏数据异步刷入磁盘。
143 12
【MySQL进阶篇】存储引擎(MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案)
MySQL的存储引擎是其核心组件之一,负责数据的存储、索引和检索。不同的存储引擎具有不同的功能和特性,可以根据业务需求 选择合适的引擎。本文详细介绍了MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案。
【MySQL进阶篇】存储引擎(MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案)
MySQL存储引擎详述:InnoDB为何胜出?
MySQL 是最流行的开源关系型数据库之一,其存储引擎设计是其高效灵活的关键。InnoDB 作为默认存储引擎,支持事务、行级锁和外键约束,适用于高并发读写和数据完整性要求高的场景;而 MyISAM 不支持事务,适合读密集且对事务要求不高的应用。根据不同需求选择合适的存储引擎至关重要,官方推荐大多数场景使用 InnoDB。
112 7
Mysql索引:深入理解InnoDb聚集索引与MyisAm非聚集索引
通过本文的介绍,希望您能深入理解InnoDB聚集索引与MyISAM非聚集索引的概念、结构和应用场景,从而在实际工作中灵活运用这些知识,优化数据库性能。
363 7

相关产品

  • 云数据库 RDS MySQL 版
  • AI助理

    你好,我是AI助理

    可以解答问题、推荐解决方案等