InnoDB记录压缩及使用分析



此文已由作者温正湖授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。



这篇文章,源于RDS组内的一次饭后闲聊,两位小伙伴在探讨InnoDB启用压缩后的种种,比如在磁盘上是怎么存放数据的,数据页的大小是多少?怎么知道一个页里面可以写入多少压缩前的数据等等。两年前曾看过InnoDB压缩的文档和代码,现在好多东西都模糊了,趁此机会,又重温了一把,下面通过问答的方式来阐述。


压缩干嘛用呢?直白点,就是为了省空间。假如你的手机是16G版iphone 6s,那么你肯定明白可用的存储空间是多么宝贵。如果能够把10G的数据变成5G甚至2G,而且还能不丢信息,那么你是不是乐翻了,做梦都在笑吧。好了,压缩就是干这事儿了。当然在iphone上压缩这招一般是行不通的,不信你试试。


压缩是百利而无一害的吗?骚年,如果有这种想法,那真是too young too simple了。虽然压缩有可能节省存储空间,但也是有代价的,压缩是要消耗cpu资源的,甚至是过多的cpu资源。目前已知的压缩算法有很多种,不同算法的压缩比和cpu消耗也是不一样,常用的算法中zlib的压缩比较高,但cpu消耗也偏高,早期程序一般采用zlib较多,如InnoDB的压缩等。snappy和quicklz在压缩比和cpu消耗上保存了较好的平衡,这两种算法在新开发的软件中使用更为广泛,包括TokuDB、MongoDB等。当然,如果你选择JPG等已经压缩过的图片进行压缩,那就是no zuo no die了,谁都救不了你。


什么样的数据适合做压缩呢?我们拿MySQL表作为例子,通常情况下表结构中包含字符型数据列如char, varchar, text或blob等时,具有较高的压缩率,而一些二进制数据,如整形或浮点型数据列,或者一些已经压缩的多媒体文档,如jpeg、jpg、png等格式图片及mp4、avi等格式视频,其压缩率都不会好。你可以从一个非压缩表中拷贝数据到一个相同的压缩表中,观察数据大小,来决定是否适合压缩。


假如我的MySQL实例的表中就存在较多字符型数据列,我想启用压缩,怎么整?不急,给你宝典,自己去练。首先你的MySQL版本必须高于5.1,现在都已经进入5.7时代了,这个不成问题。接下来把innodb_file_per_table设置为1,即除了系统表外,其他表都是单独存一个文件,还需要把innodb_file_format设置为Barracuda。然后在建新表或修改现有表的语句中加入row_format=compressed key_block_size=8就可以了。你可以仅加入row_format=compressed,这样key_block_size就取默认值8KB了。你也可以仅加入key_block_size={1/2/4/8/16},也会默认开启压缩。那么,key_block_size到底设置为多大才合理呢,这是一个深奥的问题,官方建议如下:

To determine the best value for KEY_BLOCK_SIZE, typically you create several copies of the same table with different values for this clause, then measure the size of the resulting .ibd files and see how well each performs with a realistic workload。


我的MySQL业务类型会对压缩效益有影响吗?为了尽量避免压缩、解压带来的额外消耗,InnoDB在压缩页中新增了modification log区,通过记录当前的页的修改日志,来避免频繁压缩解压。不同的业务场景,会对压缩效益产生不同的影响,下面几种场景相对适用压缩:如果业务中查询占了绝大部分,只有很少的更新,那么只有较少的页中会出现modification log空间不足,进而需要重组或者重新压缩,这种场景下是个不错的选择;如果应用只是单纯的插入,此时数据的插入按照主键递增的方式组织,即使存在二级索引也基本上不需要频繁重组和重压缩;对于删除操作,由于InnoDB删除行采用打标志位的方式来删除,对记录的删除是通过修改页中没有被压缩的元数据的方式实现,所以效率很高;对于更新操作,如果不是对索引列或者存储在off-page的blob,text,长字符串的列的更新,这种场景下使用压缩也是可以接受的。


以上说了那么多,压缩最终还是通过消耗更多的cpu资源来换取减少IO消耗,最终带来性能的提升,如果应用是IO密集型,而不是cpu密集型,那么可以利用剩余的cpu来提升应用性能。


InnoDB的压缩是怎么实现的呢?其实,截止目前InnoDB已经包括两种压缩模式,一种是这里提到的对记录(包括索引)的压缩。该压缩从MySQL 5.1开始既已存在;另一种是在MySQL 5.7中加入的,是对数据块的压缩,本文不对其做介绍。


这里对记录压缩做简单介绍:InnoDB启用压缩后,btr_page_create会调用page_create_zip从磁盘分配一张空的大小为zip_size(即key_block_size)的压缩页,该页在buffer pool(bp)中驻留,不断接收插入及更新等操作的数据,InnoDB的flush或checkpoint机制将该页数据写回磁盘,在回刷前会调用page_zip_compress进行压缩后,其实也不会每次写入磁盘都会进行压缩,就像之前问答中已经提到的,InnoDB压缩页中开辟有modification log区,压缩页中一段时间内的记录增删改会先写入该区,再通过一定条件触发合并,合并的过程就是将原来的压缩数据解压,再将modification log区的内容合并掉,重新进行压缩。随着数据的不断写入,到了一个时间点,总会出现压缩后的数据大小仍超过了设定的key_block_size,这种场景下会触发压缩页的分裂,此时就需要分配一个新页,将原页部分数据移到新页中,再分别进行压缩。如此,周而复始。


在内存充足的情况下,InnoDB bp中包括压缩页的压缩状态(buf_page_t*)page->zip.data和解压状态(buf_block_t*)page->frame。当内存不足时,InnoDB可能将解压的frame回收,保留压缩的zip.data在bp中。如果一个压缩页在一段时间内没有被使用,压缩格式的zip.data也会被写回到磁盘中,以释放内存。


Innodb使用一个自适应 LRU算法来维持bf内zip.data和frame的平衡,目的是为了避免在cpu繁忙时减少解压页的开销,当cpu富余时避免过多的IO。当系统是IO密集型时,倾向于回收frame,以留下内存给其他从磁盘读入的页。当cpu密集型时,InnoDB选择全部回收zip.data和frame,也就是回收整个压缩页,这样内存可以更多的留给热点数据,并减少内存中需要解压的页。


最后,如何逼格更高得使用InnoDB压缩呢? InnoDB一如既往得友好,information_schema中提供了两组系统表来查看运行时的压缩行为。

系统表innodb_cmp和innodb_cmp_reset用来分析运行中的压缩状态,包括压缩页的压缩/解压次数,所花费时间,压缩成功次数以及压缩页的大小等。其中innodb_cmp保存历史汇总数据,而innodb_cmp_reset则记录的是一个较为实时的统计值,表结构如下:

mysql> show create table innodb_cmp\G

*************************** 1. row ***************************

Table: INNODB_CMP
Create Table: CREATE TEMPORARY TABLE `INNODB_CMP` (
  `page_size` int(5) NOT NULL DEFAULT '0',
  `compress_ops` int(11) NOT NULL DEFAULT '0',
  `compress_ops_ok` int(11) NOT NULL DEFAULT '0',
  `compress_time` int(11) NOT NULL DEFAULT '0',
  `uncompress_ops` int(11) NOT NULL DEFAULT '0',
  `uncompress_time` int(11) NOT NULL DEFAULT '0'
) ENGINE=MEMORY DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
 
mysql> show create table innodb_cmp_reset\G

*************************** 1. row ***************************

Table: INNODB_CMP_RESET
Create Table: CREATE TEMPORARY TABLE `INNODB_CMP_RESET` (
  `page_size` int(5) NOT NULL DEFAULT '0',
  `compress_ops` int(11) NOT NULL DEFAULT '0',
  `compress_ops_ok` int(11) NOT NULL DEFAULT '0',
  `compress_time` int(11) NOT NULL DEFAULT '0',
  `uncompress_ops` int(11) NOT NULL DEFAULT '0',
  `uncompress_time` int(11) NOT NULL DEFAULT '0'
) ENGINE=MEMORY DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

如果compress_ops_ok/compress_ops的比率很高的话,则表明系统运行的良好,反之则表明InnoDB经常进行reorganize, recompress和split,此时对该表禁用压缩或者选择更大的key_block_size。

InnoDB bp使用伙伴分配系统(buddy allocator)来分配不同大小的内存页用来缓存压缩数据:1KB到16KB(即对应不同的key_block_size),information_schema为此提供了innodb_cmpmem和innodb_cmpmem_reset来记录压缩页使用bp的一些信息,包括:bp为压缩页分配的正在使用的页数,可供使用的空闲页数,伙伴系统重新分配页的次数及时间等,详细表结构如下:

mysql> show create table innodb_cmpmem\G

*************************** 1. row ***************************

Table: INNODB_CMPMEM
Create Table: CREATE TEMPORARY TABLE `INNODB_CMPMEM` (
  `page_size` int(5) NOT NULL DEFAULT '0',
  `buffer_pool_instance` int(11) NOT NULL DEFAULT '0',
  `pages_used` int(11) NOT NULL DEFAULT '0',
  `pages_free` int(11) NOT NULL DEFAULT '0',
  `relocation_ops` bigint(21) NOT NULL DEFAULT '0',
  `relocation_time` int(11) NOT NULL DEFAULT '0'
) ENGINE=MEMORY DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
 
mysql> show create table innodb_cmpmem_reset\G

*************************** 1. row ***************************

Table: INNODB_CMPMEM_RESET
Create Table: CREATE TEMPORARY TABLE `INNODB_CMPMEM_RESET` (
  `page_size` int(5) NOT NULL DEFAULT '0',
  `buffer_pool_instance` int(11) NOT NULL DEFAULT '0',
  `pages_used` int(11) NOT NULL DEFAULT '0',
  `pages_free` int(11) NOT NULL DEFAULT '0',
  `relocation_ops` bigint(21) NOT NULL DEFAULT '0',
  `relocation_time` int(11) NOT NULL DEFAULT '0'
) ENGINE=MEMORY DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

在使用InnoDB压缩时,周期性观察information_schema中提供的这两组压缩相关的临时表,对于评估压缩的效果和性能非常有参考价值。


网易云免费体验馆,0成本体验20+款云产品! 

更多网易技术、产品、运营经验分享请点击


相关文章:
【推荐】 Shadowsocks原理详解(下篇)
【推荐】 BRVAH(让RecyclerView变得更高效)(1)
【推荐】 上云、微服务化和DevOps,少走弯路的办法

posted @ 2018-10-18 15:52  网易数帆  阅读(187)  评论(0编辑  收藏  举报