脏页刷入磁盘
学习检测
-
什么是脏页?
-
什么情况触发刷脏页行为?
-
刷脏页行为对性能的消耗对比?
-
InnoDB刷脏页的策略?影响磁盘刷脏页的因素?
-
刷新脏页速度的策略?
-
为什么不直接淘汰内存,加载数据的时候,直接用redo log来校验?
-
什么事刷脏页 连坐行为?
-
脏页发现标准?
总结
-
当内存中的数据和磁盘中的数据不一致的时候,就是”脏页”
-
触发刷脏页的情况
内存满了
redo log 满了
服务正常关闭
mysql空闲时候自己刷新
-
内存满了
当有新数据的查询,不在buffer pool中的时候,就需要申请一个新的page页,如果没有未使用page和干净的page就需要将”脏页”刷新到磁盘,如果查询的数据较多,刷脏页的行为事件较长
redo log 满了
redo log 满了的时候系统会停止所有的写操作,影响业务
redo log 满了需要将checkpoint 指针向前推进,writepoint 和 checkpoint之间的就是可以写的部分
服务关闭 和 mysql “空闲” 对性能没什么影响
-
mysql 刷脏页的策略 innodb_io_capacity 和磁盘IO能力有关,设置合理值(每秒系统IO能力),影响刷脏页的因素有buffer pool最大脏页比例 redo log 空闲空间(越大)刷的越快
-
max(脏页比例 or redo log 可写空间的最大值的比例) 来刷脏页
-
数据操作是在buffer pool ,第一种情况,如果在内存中,直接操作,返回结果,不在内存,读取磁盘,加载到内存,返回结果,如果有redo log 的话,就要和redo log 进行校验在返回,redo log 也是文件形式,并且是物理逻辑日志,会更消耗性能
-
脏页有可能有连坐,发现自己的邻居有脏页也会刷,控制参数是
innodb_flush_neighbors
-
每个数据页都有头部LSN,8字节,每次修改都会变大,对比LSN跟checkoutpoint中值小的,一定是干净页,checkoutpoint 的变大说明有新事物增加了,page 小的话说明内存页是操作
buffer pool 参数
新数据写入和flush示意图
flush 触发情况
redo log 满了
InnoDB的redo log 写满了,这时候系统会停止所有的更新操作,把chekpoint往前推进(redo log 日志写入磁盘),redo log 留出空间继续写
checkpoint 可不是随便往前修改一下位置就可以的。比如图 2 中,把 checkpoint 位置从 CP 推进到 CP’,就需要将两个点之间的日志(浅绿色部分),对应的所有脏页都 flush 到磁盘上。之后,图中从 write pos 到 CP’之间就是可以再写入的 redo log 的区域。
系统内存满了
对应的就是系统内存不足。当需要新的内存页,而内存不够用的时候,就要淘汰一些数据页,空出内存给别的数据页使用。如果淘汰的是“脏页”,就要先将脏页写到磁盘。
mysql 认为系统空闲
当然,MySQL忙起来可是会很快就能把redo log 记满的,所以要合理地安排时间,即使是忙的时候,也要见缝插针地找时间,只要有机会就刷一点“脏页”。
mysql 正常关闭的情况
MySQL 正常关闭的情况。这时候,MySQL 会把内存的脏页都 flush 到磁盘上,这样下次 MySQL 启动的时候,就可以直接从磁盘上读数据,启动速度会很快。
四种情况的性能对比
第三种,在mysql”空闲的时候”,这时系统没什么压力,而第四种是数据库本来就要关闭,这两种情况下,你不会太关注”性能问题”,所以这里,我们主要分析一下前两种场景下的问题
第一种是redo log 写满,要”flush脏页”,这种情况是InnoDB要尽量避免的,因为出现这种情况,整个系统就不能接受更新了,所有的更新都必须堵住,如果你从监控上看,这时候更新数为0
第二种是”内存不够用了,先将脏页写到磁盘”,这种情况是常态,InnoDB用缓存池(buffer pool)管理内存,缓冲池中页的状态有三种
第一种,还没使用的
第二种,使用了并且是干净的
第三种,使用了并且是脏页
InnoDB的策略是尽量使用内存,因此对于一个长时间运行的库来说,未被使用的页面很少
而当要读入的数据页没有在内存的时候,就必须到申请池中去申请一个数据页,这时候只能把最久不使用的数据页从内存中淘汰掉;如果要淘汰的是一个干净页,就直接释放出来复用;但如果是脏页呢,就必须将脏页先刷到磁盘,变成干净页才能复用
所以,刷脏页是常态,但是出现以下情况时候,都会明显影响性能的
-
一个查询要淘汰的脏页个数较多,会导致查询的响应时间明显变长
-
日志写满,更新全部堵住,写性能跌为0,这种情况对敏感业务来说,是补鞥被接受的
所以InnoDB需要有控制脏页比例的机制,来避免上面情况
InnoDB 刷脏页的控制策略
innodb_io_capacity flush 刷磁盘能力(系统每秒I/O数)
InnoDB IO 能力
查看系统磁盘的IOPS
fio工具
fio -filename=$filename -direct=1 -iodepth 1 -thread -rw=randrw -ioengine=psync -bs=16k -size=500M -numjobs=10 -runtime=10 -group_reporting -name=mytest
影响磁盘刷脏页的因素
脏页比例
redo log写盘速度
刷新脏页速度策略
计算脏页比例和redo log 日志序号和当前之日序号差值,取最大值%进行刷盘操作
innodb_max_dirty_pages_pct
脏页比例上限 默认值是75%
InnoDB会根据当前的脏页比例(假设为M),算出一个范围在0–100之间的数字
伪代码
F1(M)
{
if M>=innodb_max_dirty_pages_pct then
return 100;
return 100*M/innodb_max_dirty_pages_pct;
}
InnoDB 每次写入的日志都有一个序号,当前写入的序号跟 checkpoint 对应的序号之间的差值,我们假设为 N。InnoDB 会根据这个 N 算出一个范围在 0 到 100 之间的数字,这个计算公式可以记为 F2(N)。F2(N) 算法比较复杂,你只要知道 N 越大,算出来的值越大就好了。
然后,根据上述算得的 F1(M) 和 F2(N) 两个值,取其中较大的值记为 R,之后引擎就可以按照 innodb_io_capacity 定义的能力乘以 R% 来控制刷脏页的速度。
上述的计算流程比较抽象,不容易理解,所以我画了一个简单的流程图。图中的 F1、F2 就是上面我们通过脏页比例和 redo log 写入速度算出来的两个值。
现在你知道了,InnoDB 会在后台刷脏页,而刷脏页的过程是要将内存页写入磁盘。所以,无论是你的查询语句在需要内存的时候可能要求淘汰一个脏页,还是由于刷脏页的逻辑会占用 IO 资源并可能影响到了你的更新语句,都可能是造成你从业务端感知到 MySQL“抖”了一下的原因。
要尽量避免这种情况,你就要合理地设置 innodb_io_capacity 的值,并且平时要多关注脏页比例,不要让它经常接近 75%。其中,脏页比例是通过 Innodb_buffer_pool_pages_dirty/Innodb_buffer_pool_pages_total 得到的,具体的命令参考下面的代码:
mysql> select VARIABLE_VALUE into @a from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_dirty';
select VARIABLE_VALUE into @b from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_total';
select @a/@b;
为什么不直接淘汰内存,读磁盘和redo log merge 操作
数据操作数据页有两种状态 保证内存中或者磁盘中的数据某一个是最正确的
-
数据在内存页中,内存中就是正确的结果,直接返回
-
数据不在内存页,文件上的为正确的,读入内存在处理,
如果直接淘汰内部存,数据页不在内存,读磁盘到内存,然后在和redo log 进行merge过程,会增加性能消耗,不能保证内存是最新的
刷脏页”连坐"控制
innodb_flush_neighbors
值为1标识连坐刷脏页
0位只刷自己的
一旦一个查询请求需要在执行过程中先 flush 掉一个脏页时,这个查询就可能要比平时慢了。而 MySQL 中的一个机制,可能让你的查询会更慢:在准备刷一个脏页的时候,如果这个数据页旁边的数据页刚好是脏页,就会把这个“邻居”也带着一起刷掉;而且这个把“邻居”拖下水的逻辑还可以继续蔓延,也就是对于每个邻居数据页,如果跟它相邻的数据页也还是脏页的话,也会被放到一起刷。
在 InnoDB 中,innodb_flush_neighbors 参数就是用来控制这个行为的,值为 1 的时候会有上述的“连坐”机制,值为 0 时表示不找邻居,自己刷自己的。
找“邻居”这个优化在机械硬盘时代是很有意义的,可以减少很多随机 IO。机械硬盘的随机 IOPS 一般只有几百,相同的逻辑操作减少随机 IO 就意味着系统性能的大幅度提升。而如果使用的是 SSD 这类 IOPS 比较高的设备的话,我就建议你把 innodb_flush_neighbors 的值设置成 0。因为这时候 IOPS 往往不是瓶颈,而“只刷自己”,就能更快地执行完必要的刷脏页操作,减少 SQL 语句响应时间。
在 MySQL 8.0 中,innodb_flush_neighbors 参数的默认值已经是 0 了
脏页发现机制
每个数据页头部有LSN,8字节,每次修改都会变大。
对比这个LSN跟checkpoint 的LSN,比checkpoint小的一定是干净页
因为 事务都要写redo log ,比redo log checkpoint值小的代表还没有被写入东西