MySQL抖动的原因

我们在使用MySQL实现业务处理的时候,更多的关注可能在SQL本身上面是不是最优的。今天我们从一个很小的点去看一下MySQL的实现原理,那就是MySQL刷脏页相关的问题。

刷脏页

在MySQL中,如果内存数据页(buffer pool)和磁盘数据不一致时,这个内存数据页我们认为是“脏页”;当内存数据页 flush 到磁盘之后,内存数据页和磁盘数据一致时,此时这个内存数据页就称为“干净页”。(内存中的数据和磁盘一致就可以认为是干净页)刷脏页的过程可以参见如下所示:


从上图可以看出,更新数据时,内存更新完成+redo log 写完之后就算成功了。但是此时内存中是存在脏页的,也就是数据还没有同步到磁盘上。第二个过程就是将内存中的数据同步到磁盘上面。

触发刷脏页的条件

1、redo log 写满了。因为 redo log 是一块固定大小的磁盘空间,当redo log 写满之后,会强制触发磁盘的脏页 flush 。

2、系统内存不足。因为数据的更新会更新  内存页+redo log,此时有一个空间不足都会触发脏页 flush。

3、系统负载不高时触发。也就是系统空闲时可以 flush 一些脏页,以应对后续可能出现的高负载。

4、MySQL正常关闭。数据库正常关闭时,此时所有的数据肯定需要写入磁盘持久化。

从上面的几个条件我们可以看出,对于MySQL 中的更新数据,主要有以下 2 种状态:1、内存中有(可能是脏页或者干净页)数据,此时更新操作只需要更新内存 + 写入 redo log;2、内存中无对应的数据,此时就需要从磁盘中读取对应的数据,然后更新内存 + 写入 redo log。

对于上面可能出现MySQL抖动的条件,我们主要看前面 2 种。

对于第一种,当 redo log 写满的时候,MySQL就会拒绝服务,此时不会接受任何更新操作。因此这种情况是 MySQL 所需要避免的。

对于第二种,就是内存不够用了,这种情况出现的概率还是比较大的。InnoDB 使用 Buffer Pool 管理内存,对于 Buffer Pool 中的内存主要有以下几种状态:未被使用;已使用且是干净页;已使用且是脏页。

因此,当内存不足时,如果要申请一个内存页时,此时就需要淘汰一个最久不使用的内存页:如果是“干净页”,则直接释放出来使用;如果是“脏页”则首先需要 flush 然后再释放使用。

总结以上,MySQL发生抖动的原因主要有以下:

1、内存不足时,所需要淘汰的内存页(脏页)太多;

2、redo log 写满时,这种可能出现瞬间的拒绝服务。

InnoDB的 flush 脏页策略

innodb_io_capacity 参数,它会告诉 InnoDB 你的磁盘读写能力是多少。这个参数建议设置成机器的 IOPS ,可以通过 fio 工具来测试获得。如下所示:

$ fio -filename=data.mp4 -direct=1 -iodepth 1 -thread -rw=randrw -ioengine=psync -bs=16k -size=500M -numjobs=10 -runtime=10 -group_reporting -name=mytest

测试完成之后如下图所示:


如果 innodb_io_capacity 设置的过小,比如比正常刷脏页的速度还慢,则肯定影响正常的IO 读写。

innodb_max_dirty_pages_pct 参数: 脏页的比例,默认是 75%。

innodb_flush_neighbors 参数:刷脏页的时候,如果相邻的内存页也是脏页的话,也会flush。这个参数在机械硬盘时代,可能会减少随机IO 的开销。使用 SSD 的话建议设置成 0 ,即关闭。


参考:《极客时间:MySQL实战》、《高性能MySQL》