InnoDB的脏页刷盘时机
场景一:redo log快满了
我们知道,redo log的空间是固定大小的,那么就就有使用完毕的风险。
假设某个时刻系统疯狂的进行修改操作,那么大量的日志就会进入redo log中。此时刷盘速度(对应check point前进速度)的远远小于写盘速度(write pos前进速度)时,就会触发刷盘。
但这种刷盘的后果是非常严重的,和上图一样,这时系统就会停止所有更新操作,把checkoutpoint 往前推,redo log留出空间可以继续写。此时整个系统都不能接受更新。更新数会跌为0。后续我们必然会讲一下如何避免这个问题。
场景二:Buffer Pool内存不足时
一般会从LUA链表中,直接释放一些长期未被访问,且没有更新的操作的页(释放这种页不需要磁盘IO)。假设都是脏页,那也只能强行刷盘了。
场景三:数据库空闲时刷盘(主流刷盘方式)
场景一,场景二和大家解释了半天,但实际上绝大多数场景下,上面两种情况都是要避免的。正常来说,都是等数据库空闲的时候去刷盘,以保证MySQL一直保持一个较高的吞吐量。
场景四:数据库正常关闭时刷盘
数据库正常关闭的时候,也要把内存中所有的脏页全都flush到磁盘上。
InnoDB 刷脏页的控制策略
我们知道,如果触发了场景一,场景二的刷盘条件,就会让MySQL迫不得已做一些严重影响效率的事儿去强制刷盘。
因此,为了避免这两种情况,我们需要正确的控制 InnoDB IO 能力,来控制刷脏页的快慢。如果刷太慢,会出现什么情况?首先是内存脏页太多,其次是 redo log 写满。
基于以上的目的,InnoDB 的刷盘速度就是要参考这两个因素:一个是脏页比例,一个是 redo log 写盘速度。
基于脏页比例,计算F1
参数 innodb_max_dirty_pages_pct 是脏页比例上限,默认是 75%。InnoDB 会根据当前的脏页比例,计算出一个数字 F1。
F1(M)
{
if M>=innodb_max_dirty_pages_pct then
return 100;
return 100*M/innodb_max_dirty_pages_pct;
}
M是实际的脏盘比例。这个公式可以理解为,脏盘越多,F1越高。
基于redo log剩余空间,计算F2
InnoDB 写入日志都会有一个序号,当前写入序号跟 checkpoint 对应的序号之间的差值,假设为N。InnoDB 会根据N计算出 F2.
上面的话你可能看的有点绕。多读读总结出来就是:redo log越满,值就越大。
取F1,F2的最大值,来控制刷盘速度
根据 F1和F2 取其中较大的值为 R,之后引擎就可以按照 Innodb_io_capacity 定义的能力乘以 R% 来控制刷脏页的速度。
当然,如果开启,刷盘的速度越高,必然会影响MySQL的吞吐量。并且如果用最高的速度刷盘,依旧导致Buffer Pool或redo log满了,那也只能使用场景一,场景二这种无奈的办法刷盘了。
MySQL的刷盘优化
MySQL 中有一个机制,刷脏页的时候如果数据页旁边的数据页也是脏页,那么就会一起刷掉,而且这个逻辑是可以蔓延的,所以对于每个相邻的数据页,都会被一起刷。
在 InnoDB 中,innodb_flush_neighbors 参数就是用来控制这个行为的,值为 1 的时候会有上述的“连坐”机制,值为 0 时表示不找邻居,自己刷自己的。
在使用机械硬盘时,这个优化很有意义,可以减少很多随机 IO。如果使用的是 SSD 这种IOPS 比较高的设备,可以设置innodb_flush_neighbors 为0,只刷自己,这个时候 IOPS 往往就不是性能瓶颈了。只刷自己就可以提高刷脏页的速度,减少 SQL 语句的响应时间。(有钱烧机器还是强)