Mysql压缩解决方案

提到mysql压缩相关的内容,我们能想到的可能是如下几种和压缩相关的场景:

1、客户端和服务器之间传输的数据量太大,需要进行压缩,节约带宽

2、mysql某个列的数据量大,只针对某个列的数据压缩

3、mysql某个或者某几个表数据太多,需要将表数据压缩存放,减少磁盘空间的占用

这几个问题在mysql侧都有很好的解决方案 ,针对第1个问题,可以使用mysql的压缩协议解决;针对第2个问题,可以采用mysql的压缩和解压函数完美解决;而针对最复杂的第3个问题,则可以在引擎层面进行解决,目前myisam、innodb、tokudb、MyRocks等引擎都支持表的压缩。本篇文章要详细讨论的就是此类关于mysql压缩机制相关 的问题,下面是主要的内容:

一、mysql压缩协议介绍

1、适用场景

mysql压缩协议适合的场景是mysql的服务器端和客户端之间传输的数据量很大,或者可用带宽不高的情况,典型的场景有如下两个:

a、查询大量的数据,带宽不够(比如导出数据的时候)

b、复制的时候binlog量太大,启用slave_compressed_protocol参数进行日志压缩复制

2、压缩协议简介

压缩协议是mysql通信协议的一部分,要启用压缩协议进行数据传输,需要mysql服务器端和客户端都支持zlib算法。启动压缩协议会导致CPU负载略微上升。使用启用压缩协议使用-C参数或者 --compress=true参数启动客户端的压缩功能。如果启用了-C或者compress=true选项,那么在连接到服务器段的时候,会发送0x0020(CLIENT_COMPRESS)的服务器权能标志位,和服务器端协商通过后(3次握手以后),就支持压缩协议了。由于采用压缩,数据包的格式会发生变化,具体的变化如下:

未压缩的数据包格式:

 
 

压缩后的数据包格式:

 
 

大家可能留意到压缩后的数据报格式有压缩和未压缩之分,这个是mysql为了较少CPU开销而做的一个优化。如果内容小于50个字节的时候,就不对内容进行压缩,而大于50字节的时候,才会启用压缩功能。具体的规则如下:

当第三个字段的值等于0x00的时候,表示当前包没有压缩,因此n*byte的内容为1*byte,n*byte,即请求类型和请求内容。

当第三个字段的值大于0x00的时候,表示当前包已采用zlib压缩,因此使用的时候需要对n*byte进行解压,解压后内容为1*byte,n*byte,即请求类型和请求内容。

3、方案实践

在客户端连接的时候加上-C或者--compress=true参数。如果是对同步添加压缩协议支持的时候,则需要配置slave_compressed_protocol=1。下面是采用压缩协议连接mysql服务端的范例:

mysql -h hostip -uroot -p password --compress

mysqldump -h hostip -uroot -p password -default-character-set=utf8  --compress --single-transaction dbname tablename > tablename.sql

如果需要在主从复制中启用压缩传输,则在从机开启slave_compressed_protocol=1参数就OK。

4、压缩效果

可以通过在mysqldump中使用--compress选项来观察压缩传输的效果,也可以通过主从复制中已用slave_compressed_protocol参数来观察压缩传输的效果,很容易看出效果,这里不再截图说明。

二、mysql列压缩解决方案

mysql针对列的压缩目前直接的方案并不支持,映象中腾讯的Tmysql可以直接针对列的压缩。这里主要介绍一个曲线救国的办法,那就是在业务层面使用mysql提供的压缩和解压函数来针对列进行压缩和解压操作。也就是要对某一列做压缩,就需要在写入的时候调用COMPRESS函数对那个列的内容进行压缩,然后存放到对应的列。读取的时候,使用UNCOMPRESSED函数对压缩的内容进行解压缩。

1、适用场景

针对mysql中某个列或者某几个列数据量特别大,一般都是varchar、text、char等数据类型。

2、压缩函数简介

mysql的压缩函数COMPRESS压缩一个字符串,然后返回一个二进制串。使用该函数需要mysql服务端支持压缩,否则会返回NULL,压缩字段最好采用varbinary或者blob字段类型保存。使用UNCOMPRESSED函数对压缩过的数据进行解压。注意,采用这种方式需要在业务侧做少量改造。压缩后的内容存储方式如下:

a、空字符串就以空字符串存储

b、非空字符串存储方式为前4个bype保存未压缩的字符串,紧接着保存压缩的字符串

3、方案实践

字段压缩方案涉及到的几个相关的函数如下:

压缩函数

COMPRESS()

解压缩函数

UNCOMPRESS()

字符串长度函数

LENGTH()

未解压字符串长度函数

UNCOMPRESSED_LENGTH()

实践步骤:

a、创建一张测试表

CREATE TABLE  IF NOT EXISTS `test`.`test_compress` (

`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',

`content` blob NOT NULL COMMENT '内容列',

PRIMARY KEY (`id`)

) ENGINE=InnoDB  DEFAULT CHARSET=latin1 COMMENT='压缩测试表';

b、网表中插入压缩的数据

insert into `test`.`test_compress`(content) values(COMPRESS(REPEAT('a',1000)));

c、读取压缩的数据

select UNCOMPRESS(content) from `test`.`test_compress`;

d、查询对应的长度和内容

SELECT UNCOMPRESSED_LENGTH(content) AS length, LENGTH(content) AS compress_length, UNCOMPRESS(content), content FROM `test`.`test_compress`

4、压缩效果

 
 

从上面截图可以看出压缩效果比较好,针对text、char、varchr、blob等,如果里面重复的数据越多压缩效果就越好。

三、InnoDB表压缩方案解决方案

1、适用场景

采用压缩表一般都用在由于数据量太大,磁盘空间不足,负载主要体现在IO上,而服务器的CPU又有比较多的余量的场景。

2、表压缩简介

a、为什么需要压缩

目前很多表都支持压缩,比如Myisam、InnoDB、TokuDB、MyRocks 。由于使用InnoDB主要是不需要做什么改动,对线上完全透明,压缩方案也非常成熟,因此这里只对InnoDB做详细说明。对于TokuDB和MyRocks的压缩方案讲在mysql的压缩方案<二>中撰文说明。

在SSD没有大量横行的时候,数据库几乎都是IO负载型的,在CPU有大量余量的时候,磁盘IO的瓶颈就已经凸显出来。而数据的大量存储,尤其是日志型数据和监控类型的数据,会导致磁盘空间快速增长。硬盘不够用也会在很多业务中凸显出来。一种比较好的方式就诞生了,那就是通过牺牲少量CPU资源,采用压缩来减少磁盘空间占用,以及优化IO和带宽。尤其针对读多些少的业务。

SSD出来后,数据库的IO负载有所降低,但是对于磁盘空间的问题还是没有很好的解决。因此压缩表使用还是非常的广泛。这也就是为什么那么多的引擎都支持压缩的原因。而innodb在mysql 5.5的时候就支持了压缩功能,只是压缩比比较低,通常在50%左右。而tokuDB能达到80%左右,MyRocks的压缩比能达到70%左右。

注意:压缩比和你存储的数据组成有很大的关系,并不是所有的数据都能达到上面所说的压缩比。如果大部分都是字符串,并且重复的数据比较多,压缩比会很好。

b、innodb的压缩介绍

使用innodb压缩的前提条件是,innodb_file_per_table这个参数要启用,innodb_file_format这个参数设置成Barracuda。

你可以使用ROW_FORMAT=COMPRESSED来create或者alter表来开启innodb的压缩功能,如果没有指定KEY_BLOCK_SIZE的大小,默认KEY_BLOCK_SIZE为innodb_page_size大小的一半,也可以通过指定KEY_BLOCK_SIZE=n参数来开启innodb的压缩功能,n可以为1、2、4、8、16,单位是K。n的值越小,压缩比越高,消耗的CPU资源也越多。注意32K或者64K的页不支持压缩。启用压缩后,索引数据也同样会被压缩。

你也可以通过调整innodb_compression_level来设置压缩的级别,级别从1~9,默认是6。级别越低,意味着压缩比越高,同时也意味着需要更多的CPU资源。

c、压缩算法

innodb压缩借助的是著名的zlib库,采用L777压缩算法,这种算法在减少数据大小和CPU利用方面很成熟高效。同时这种算法是无损的,因此原生的未压缩的数据总是能够从压缩文件中重构,LZ777实现原理是查找重复数据的序列号然后进行压缩,所以数据模式决定了压缩效率,一般而言,用户的数据能够被压缩50%以上。

d、压缩表在buffer_pool中如何处理

在buffer_pool缓冲池中,压缩的数据通过KEY_BLOCK_SIZE的大小的页来保存,如果要提取压缩的数据或者要更新压缩数据对应的列,则会创建一个未压缩页来解压缩数据,然后在数据更新完成后,会将为压缩页的数据重新写入到压缩页中。内存不足的时候,mysql会讲对应的未压缩页踢出去。因此如果你启用了压缩功能,你的buffer_pool缓冲池中可能会存在压缩页和未压缩页,也可能只存在压缩页。不过可能仍然需要将你的buffer_pool缓冲池调大,以便能同时能保存压缩页和未压缩页。

mysql采用最少使用(LRU)算法来确定将哪些页保留在内存中,哪些页剔除出去,因此热数据会更多地保留在内存中。当压缩表被访问的时候,mysql使用自适应的LRU算法来维持内存中压缩页和非压缩页的平衡。当系统IO负载比较高的时候,这种算法倾向于讲未压缩的页剔除,一面腾出更多的空间来存放更多的压缩页。当系统CPU负载比较高的时候,mysql倾向于将压缩页和未压缩页都剔除出去,这个时候更多的内存用来保留热的数据,从而减少解压的操作。

e、如何评估KEY_BLOCK_SIZE是否合适

为了更深入地了解压缩表对性能的影响,在Information Schema库中有对应的表可以用来评估内存的使用和压缩率等指标。INNODB_CMP是收集的是某一类的KEY_BLOCK_SIZE压缩表的整体状况的信息,汇总的是所有KEY_BLOCK_SIZE压缩表的统计。而INNODB_CMP_PER_INDEX表则是收集各个表和索引的压缩情况信息,这些信息对于在某个时间评估某个表的压缩效率或者诊断性能问题很有帮助。INNODB_CMP_PER_INDEX表的收集会导致系统性能受到影响,必须innodb_cmp_per_index_enabled选项才会记录,生产环境最好不要开启。

我们可以通过观察INNODB_CMP表的压缩失败情况,如果失败比较多,则需要调大KEY_BLOCK_SIZE。一般建议KEY_BLOCK_SIZE设置为8。

3、方案实践

a、设置好innodb_file_per_table和innodb_file_format参数

SET GLOBAL innodb_file_per_table=1;SET GLOBAL innodb_file_format=Barracuda;

b、创建对应的压缩表

CREATE TABLE compress_test (c1 INT PRIMARY KEY,content varchar(255)) ROW_FORMAT=COMPRESSEDKEY_BLOCK_SIZE=8;

如果是已经存在的表,则通过alter来修改,SQL如下:

ALTER TABLEcompress_testROW_FORMAT=COMPRESSEDKEY_BLOCK_SIZE=8;

4、压缩效果

压缩效果通过线上的一个监控的表修改为压缩后的文件大小来说明,压缩前后对比如下:

 
 

四、参考文献

https://dev.mysql.com/doc/refman/5.7/en/innodb-compression-background.html

https://dev.mysql.com/doc/refman/5.7/en/general-tablespaces.html#general-tablespaces-creating

http://www.tocker.ca/2013/10/31/benchmarking-innodb-page-compression-performance.html

http://www.cnblogs.com/mysql-dba/p/5125220.html

http://hutaow.com/blog/2013/11/06/mysql-protocol-analysis/#11

https://my.oschina.net/tangcoffee/blog/362382

http://www.cnblogs.com/lispking/p/3604063.html

https://dev.mysql.com/doc/refman/5.7/en/innodb-table-compression.html

https://dev.mysql.com/doc/refman/5.7/en/innodb-compression-internals.html



作者:飞鸿无痕
链接:https://www.jianshu.com/p/d7cc90218222
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
原文地址:https://www.cnblogs.com/DataArt/p/9881700.html