MySQL深入10-脏页刷新
# MySQL深入-脏页刷新
在redo log中有一个关键机制:WAL
,它类似于草稿本的形式,减少了写磁盘的次数,大大提高了数据库磁盘I/O操作的效率,从而提高了数据库的性能。
但是草稿本的内容终究是要写入试题卷,相当于内存里的数据终究要写入磁盘。这个过程就是flush
。在flush
之前,内存里的数据和磁盘里的数据是不一致的。
脏页: 当内存数据页跟磁盘数据页内容不一致的时候,这个内存页被称为脏页
。
干净页: 内容数据写入磁盘后,内存和磁盘上的数据页内容一致后,这个内存页就被称为干净页
。
平时执行很快的更新操作,其实就是在写内存和日志,而MySQL偶尔抖
一下的瞬间,可能就是在刷脏页(flush)。
# 脏页刷新的四种场景
# 场景1-redo log写满
场景1就好比在只有一张草稿纸的情况下,如果草稿纸已经写满了,你就只能把草稿纸一部分内容记录到试题卷上,记录完毕之后再擦掉这部分内容。
这个场景,对应的就是 InnoDB 的 redo log 写满了。这时候系统会停止所有更新操作,把 checkpoint 往前推进,redo log 留出空间可以继续写。
在上图中,checkpoint 位置从 CP 推进到 CP’,就需要将两个点之间的日志(浅绿色部分)所对应的所有脏页都flush到磁盘上。之后,图中write pos到CP’之间就是可以再写入到redo log的区域。
# 场景2-内存不足
假设A的记忆最多能记10件事情,现在已经记下了7件事情,现在又来了5件事情需要记录,由于记忆不到那么多,就需要把之前记下的事情先写入日记本。这个记忆就相当于内存,记忆不下那么多,相当于内存不够。
当需要新的内存页,而内存不够用的时候,就要淘汰一些数据页,空出内存给别的数据页使用。如果淘汰的是“脏页”,就要先将脏页写到磁盘。
# 场景3-空闲状态
MySQL认为系统空闲的时候,或者即使MySQL比较忙,但是也会忙中偷闲,只要找到机会就刷一点脏页
。
# 场景4-关闭
MySQL 正常关闭的情况。这时候,MySQL会把内存的脏页都 flush 到磁盘上,这样下次 MySQL 启动的时候,就可以直接从磁盘上读数据,启动速度会很快。
# 四种场景的性能影响
由于场景3是空闲状态,场景4是临近关闭状态,这两种场景是不太需要关注性能问题的,所以我们重点分析场景1和场景2。
场景1
redo log写满了,要flush脏页。这种情况是需要避免的。因为在这种情况下,系统是暂时不接受更新的,所有的更新相当于阻塞了。
场景2
内容不够用了,先将脏页写到磁盘。这种情况是常态。
InnoDB用缓冲池(buffer pool)管理内存,缓冲池的内存页有三种状态:
- 没有使用的
- 使用了并且是干净页
- 使用了并且是脏页
InnoDB的策略是尽量使用内存,因此对于一个长时间运行的库来说,未被使用的页面很少。
如果要读入的数据页没有在内存中,就必须到缓冲池中申请一个数据页。这时候只能把最久不使用的数据页从内存中淘汰掉:
- 如果淘汰的是一个干净页,就直接释放出来复用。
- 如果是脏页,就必须将脏页刷到磁盘,变成干净页才能复用。
在刷新脏页的时候,如下的情况会明显影响性能:
- 一个查询要淘汰的脏页个数,就会导致查询的时间响应变长。
- 日志写满,更新全部堵住,写性能跌为0。
因此需要一个控制脏页比例的机制,来避免上面出现的情况。
# InnoDB刷脏页的控制策略
首先,要正确地告诉 InnoDB 所在主机的 IO 能力,这样 InnoDB 才能知道需要全力刷脏页的时候,可以刷多快。这就要用到 innodb_io_capacity 这个参数了,它会告诉 InnoDB 你的磁盘能力。这个值建议设置成磁盘的 IOPS。
现在已经定义了全力刷脏页
的行为,现在我们就需要InnoDB按照全力
的百分比刷脏页,这样把一部分磁盘能力去服务用户请求之类的。
我们需要避免的就是:1、内存脏页太多。2、redo log写满。所以InnoDB需要控制的就是脏页的比例和redo log写磁盘的速度。
参数 innodb_max_dirty_pages_pct 是脏页比例上限,默认值是 75%。InnoDB 会根据当前的脏页比例(假设为 M),算出一个范围在 0 到 100 之间的数字
InnoDB 每次写入的日志都有一个序号,当前写入的序号跟 checkpoint 对应的序号之间的差值,我们假设为 N。InnoDB 会根据这个 N 算出一个范围在 0 到 100 之间的数字,这个计算公式可以记为 F2(N)
然后,根据上述算得的 F1(M) 和 F2(N) 两个值,取其中较大的值记为 R,之后引擎就可以按照 innodb_io_capacity 定义的能力乘以 R% 来控制刷脏页的速度。
InnoDB会在后台刷脏页,而刷脏页的过程就是要将内容页写入磁盘。所以,无论是查询语句在需要内存的时候要求淘汰一个脏页,还是由于刷脏页的逻辑会占用IO资源并影响到更新语句,都可能造成业务端感知到抖了一下。
刷新的策略
在准备刷一个脏页的时候,如果这个数据页旁边的数据页刚好是脏页,就会把这个“邻居”也带着一起刷掉;而且这个把“邻居”拖下水的逻辑还可以继续蔓延,也就是对于每个邻居数据页,如果跟它相邻的数据页也还是脏页的话,也会被放到一起刷。
在 InnoDB 中,innodb_flush_neighbors 参数就是用来控制这个行为的,值为 1 的时候会有上述的“连坐”机制,值为 0 时表示不找邻居,自己刷自己的。