MySQL 8.0踩坑之双写负优化

       最近,在MySQL中LOAD数据的时候遇到了一个诡异的问题,LOAD一个1G的文件(LOAD到表之后,表的数据文件约2G)。在5.7版本中一切正常,不到3分钟完成。将版本切换到8.0之后,性能急剧下降,1个小时也没有完成任务。

       MySQL 5.7和8.0都使用的是同一台虚拟机,并且在部署MySQL时,都是基于相同的保存点进行,所以可以基本上可以排除是虚拟机和操作系统的问题。

       通过对比5.7与8.0的配置,除了两者互不存在的一些配置项,以及一些明显与性能无关的杂项,也确定他们在配置是相同的。

       随后通过在不同配置环境中的多次测试验证,最终确认导致这个问题的元凶是MySQL的双写机制:自MySQL 8.0.20起,双写buffer从共享表空间剥离,使用单独的文件存储(详情参考本文后面的附图),并且增加了几个相关的参数来细化双写的处理方式,问题就在新的控制参数上。

       对比mysql5.7和8.0.20+,它们的双写相关参数如下表所示:

5.7

8.0.20+

innodb_doublewrite

ON

ON

innodb_doublewrite_batch_size

不支持

0

innodb_doublewrite_dir

不支持

innodb _ data _ home _ dir

innodb_doublewrite_files

不支持

innodb_buffer_pool_instances * 2

innodb_doublewrite_pages

不支持

innodb_write_io_threads

innodb_buffer_pool_instances

8(or 1 if innodb_buffer_pool_size < 1GB)

innodb_write_io_threads

4

       参照照mysql8.0官方文档的说明,除了innodb_doublewrite_dir,其他参数适用于大多数场景,并且不建议用户调整。所以似乎我们不需要关注它,但问题恰好出在不建议配置的参数innodb_doublewrite_pages上。对于这个参数,官方文档的说明是:maximum number of doublewrite pages per thread for a batch write,其默认值等于innodb_write_io_threads(该参数的默认值为4)。

    这是一个非常诡异的默认值。我们知道,双写无疑会增加磁盘IO压力的,双写是在写数据到数据文件之前,增加了写双写Buffer(内存+落盘到文件)这一操作。为了尽可能降低性能影响,双写在buffer落盘的时候,使用批量+顺序写的方式进行。对于Buffer落盘,使用独立的文件还是共享表空间,并不影响顺序写这个特性,而批量写方面,在8.0.20之前,是每批64个Page, 8.0.20及之后的版本,则由参数innodb_doublewrite_pages确定,默认值是4。对于LOAD这种存在大量需要落盘的情况,64和4的差异对比,无疑将后者的落盘次数增加了16倍,而落盘所对应的fsync操作是非常影响性能的,所以16倍的差异无疑是导致此次问题的元凶。

    在确认问题后,将innodb_doublewrite_pages调整为64,LOAD的性能恢复正常。

    至于设计者为何将innodb_doublewrite_pages与innodb_write_io_threads划等号,其原因目前尚无法理解,两者之间似乎找不到什么必然的因果关系,但无论是其实际表现,还是做原理分析,其都是一个较为明显的负优化设置。

小知识:

1、 什么是MySQL双写?

        MySQL以数据页(默认16K)为单位进行磁盘写入,而磁盘系统的最小写入单位是512字节,在数据页的写入过程中,如果宕机,则数据页可能出现没有完整写入的情况,这将导致数据页损坏。为了避免这个问题,MySQL innodb使用双写机制来解决这个问题,即:

A、 被修改的数据页在写入数据文件之前,先将其放入双写buffer(内存),然后落盘(将Buffer中的数据顺序写入对应的文件)。双写buffer成功落盘后,再将数据页写入对应的数据文件(离散写);

B、 如果故障出现在双写buffer落盘的时候,则可以通过redo log实现故障恢复(此时数据文件中的对应数据页是完好的);

C、 如果故障出现在写数据文件,则可以从双写buffer中COPY正确的数据页来完成故障恢复。

2、 为什么不能直接从redo log中恢复损坏的数据页?

        Redo log中不包含完整的数据页(性能考虑),通常只包含对数据页的变更记录。所以通过完好的原始数据页结合redo log中的记录,可以推断导出变更后的正确数据页,而一旦原始数据页损坏,则无法推导出正确的变更后的数据页。

3、 双写是否会大大降低写性能?

       通常不会。假设要写入磁盘的数据页有64页,那么直接写数据文件会产生64次fsync,使用双写的情况下,假设双写Buffer落盘的批大小为64页,则落盘只需要1次fsync(总体fync加1),并且双写Buffer落盘是顺序写文件,也保障了文件写入的性能。所以通常情况下,使用双写并不会大大降低写性能。

    双写对性能影响的关键就是Buffer落盘的批大小,8.0.20及之后的版本,通过参数innodb_doublewrite_pages控制(之前的版本则固定为64)。所以自8.0.20开始的版本,将其设置为一个合理值显得极其重要(目前的默认配置显然是不合理的)。MySQL提供了两个状态值:Innodb_dblwr_pages_written、innodb_dblwr_writes,前者为累计的双写页数,后者为累计的双写次数。两值的比例,可用于衡量innodb_doublewrite_pages设置是否合理,如果两值的比例经常接近innodb_doublewrite_page,并且磁盘的吞吐量明显低于磁盘的最佳性能表现,则表明innodb_doublewrite_page设置得过低。

附图:mysql5.7与8.0的InnoDB架构图(取自mysql官网)

       对比其中的DoubleWrite Buffer(红框标识),可以看出DoubleWrite Buffer从ibdata1(5.7)迁移到了独立文件(8.0.20+)

MySQL 5.7 InnoDB Architecture

MySQL 8.0踩坑之双写负优化_第1张图片

MySQL 8.0 InnoDB Architecture

MySQL 8.0踩坑之双写负优化_第2张图片

 

【本文已优先在公司公众号发表】

你可能感兴趣的:(MySQL,mysql,数据库,database)