InnoDB刷脏页策略

什么是脏页?为什么会有脏页?

前文已介绍过,Mysql有Write Ahead Logging 机制,InnoDB 在处理更新语句的时候,只做了写日志redo log这一个磁盘操作。而将内存里的数据写入磁盘的过程,术语就是 flush。在flush 操作执行之前,内存与磁盘数据是不一致的。

当内存数据页跟磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”。 内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”。

什么情况下会刷脏页?

刷脏页有下列四种场景: 一、 redo log 写满了,要 flush 脏页

redo log 写满了,系统会停止所有更新操作,把 checkpoint 往前推进,redo log 留出空间可以继续写。 需要将两个点之间的日志(要推进的部分),对应的所有脏页都 flush 到磁盘上。 innodb要尽量避免,会堵住所有更新。

二、内存不够用了,要先将脏页写到磁盘

这种情况是常态。InnoDB 用缓冲池(buffer pool)管理内存,缓冲池中的内存页有三种状态:

  1. 还没有使用的;

  2. 使用了并且是干净页;

  3. 使用了并且是脏页。

InnoDB 的策略是尽量使用内存,因此对于一个长时间运行的库来说,未被使用的页面很少。 而当要读入的数据页没有在内存的时候,就必须到缓冲池中申请一个数据页。这时候只能把最久不使用的数据页从内存中淘汰掉:如果要淘汰的是一个干净页,就直接释放出来复用;但如果是脏页呢,就必须将脏页先刷到磁盘,变成干净页后才能复用。

三、 mysql认为系统空闲,flush脏页

四、 mysql正常关闭

刷脏页虽然是常态,但是出现以下这两种情况会导致性能问题

  1. 一个查询要淘汰的脏页个数太多,会导致查询的响应时间明显变长;

  2. 日志写满,更新全部堵住,写性能跌为 0,这种情况对敏感业务来说,是不能接受的。

InnoDB刷脏页控制策略与参数

innodb_io_capacity 参数建议设置成磁盘的 IOPS

#测试磁盘随机读写的命令
fio -filename=$filename -direct=1 -iodepth 1 -thread -rw=randrw -ioengine=psync -bs=16k -size=500M -numjobs=10 -runtime=10 -group_reporting -name=mytest

innodb_io_capacity 设置错误案例: 主机磁盘用的是 SSD,但是innodb_io_capacity的值设置的是 300。于是,InnoDB 认为这个系统的能力就这么差,所以刷脏页刷得特别慢,甚至比脏页生成的速度还慢,这样就造成了脏页累积,影响了查询和更新性能。

如果设计策略控制刷脏页的速度,应该参考哪些因素呢?

刷太慢,会出现什么情况?首先是内存脏页太多,其次是 redo log 写满。所以,InnoDB 的刷盘速度就是要参考这两个因素:

  1. 脏页比例

  2. redo log 写盘速度。

innodb_max_dirty_pages_pct是脏页比例上限,默认值是 75%。

通过脏页比例和 redo log 写入速度算出来的两个值,取其中较大值R, 引擎就可以按照innodb_io_capacity定义的能力乘以 R% 来控制刷脏页的速度。

InnoDB 会在后台刷脏页,而刷脏页的过程是要将内存页写入磁盘。所以,无论是你的查询语句在需要内存的时候可能要求淘汰一个脏页,还是由于刷脏页的逻辑会占用 IO 资源并可能影响到了你的更新语句,都可能是造成你从业务端感知到 MySQL“抖”了一下的原因。

刷脏页“连坐”参数

MySQL 中的一个机制,在准备刷一个脏页的时候,如果这个数据页旁边的数据页刚好是脏页,就会把这个“邻居”也带着一起刷掉;而且这个把“邻居”拖下水的逻辑还可以继续蔓延,也就是对于每个邻居数据页,如果跟它相邻的数据页也还是脏页的话,也会被放到一起刷。InnoDB 中,innodb_flush_neighbors = 1 会有上述的“连坐”机制,为0不会。

这种机制在机械硬盘上,可以减少很多随机 IO,提升系统性能。而使用SSD这类IOPS高的设备,建议设置成0,此时IOPS 往往不是瓶颈,可以设置“只刷自己”减少 SQL 语句响应时间。

参考: 《mysql实战45讲》丁奇

Last updated

Was this helpful?