Scott's world.

MySQL-脏页相关笔记

Word count: 2.2kReading time: 7 min
2019/11/24 Share

MySQL-脏页相关笔记

回顾前面我们提到的WAL(write ahead logging)机制

即InnoDB在处理更新语句的时候,只做了写日志这一个磁盘操作。这个日志叫作redo log(重做日志)

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

不论是脏页还是干净页,都在内存中。

每个数据页头部有LSN,8字节,每次修改都会变大。

对比这个LSN跟checkpoint 的LSN,比checkpoint小的一定是干净页

在我们平常执行相关的更新操作,其实就是在写内存和日志,而偶尔我们可能遇到更新语句的执行效率突然变得非常慢时,可能就是在刷脏页即flush时。

引发flush过程

我们在什么情况会引发flush过程呢

将脏页flush到磁盘上是直接将脏页数据覆盖到对应磁盘上的数据

  • 第一种场景

    InnoDB的redo log写满了。这个时候系统会停止所有的更新操作,把checkpoint往前推进,redo log留出空间可以继续写。

    redo log是循环进行写操作的,即从头开始写,写到末尾后就回到开头继续写

    而checkpoint往前推进时,就是将推进部分对应记录的所有脏页flush到磁盘上,而flush过后就会留出相应空间

    redo log空间循环使用就无所谓释放,即相应脏页也就会变成干净页,最后等待淘汰的时候才会逐出内存

    涉及到的其他操作:

    1.把相对应的数据页中的脏页持久化到磁盘,checkpoint往前推
    2.由于redo log还记录了undo的变化,undo log buffer也要持久化进undo log
    3.当innodb_flush_log_at_trx_commit设置为非1,还要把内存里的redo log持久化到磁盘上
    4.redo log还记录了change buffer的改变,那么还要把change buffer purge到idb
    以及merge change buffer.merge生成的数据页也是脏页,也要持久化到磁盘
    上述4种操作,都是占用系统I/O,影响DML,如果操作频繁,会导致’抖’得向现在我们过冬一样。

  • 第二种场景

    当需要新的内存页时系统内存不足,这时候就需要淘汰一些数据页,空出的内存给别的数据页使用,而如果淘汰的是脏页则先将脏页写到磁盘上。

    在考虑到刷脏页一定会写盘时,就保证了每个数据页有两种状态:

    • 内存里存在,内存里就肯定是正确结果,直接返回
    • 内存里没有数据,就可以肯定数据文件上是正确的结果,读入内存后返回。这样的效率最高。
  • 第三种场景

    MySQL认为系统“空闲”的时候进行flush操作,这时候系统没什么压力就可以利用起来进行flush操作

  • 第四种场景

    MySQL正常关闭时会把内存的脏页都flush到磁盘上,这样下次启动的时候就可以直接从磁盘上读数据,加快启动速度

在上面四种场景中后面两种其实时比较与“性能”问题相关不大,而我们需要注意的是前面两种场景下的性能问题

  • 当redo log写满了要flush脏页的情况是InnoDB要尽量避免的,因为在此情况下,整个系统就不能再接受更新了,所有的更新都必须堵住,即更新数为0

  • 内存不够用了要先将脏页写到磁盘的情况其实是常态

    InnoDB用缓冲池(buffer pool)管理内存,缓冲池中的内存页有三种状态:

    1. 还没有使用的
    2. 使用了并且是干净页
    3. 使用了并且是脏页

    InnoDB的策略是尽量使用内存,因此对于一个长时间运行的库来说,未被使用的页面很少

    而当要读入的数据页没有在内存的时候,就必须到缓冲池中申请一个数据页。这时候只能把最久不使用的数据页从内存中淘汰掉:如果要淘汰的是一个干净页,就直接释放出来复用;但如果是脏页呢,就必须将脏页先刷到磁盘,变成干净页后才能复用。

    所以,刷脏页虽然是常态,但是出现以下这两种情况,都是会明显影响性能的:

    1. 一个查询要淘汰的脏页个数太多,会导致查询的响应时间明显变长;
    2. 日志写满,更新全部堵住,写性能跌为0,这种情况对敏感业务来说,是不能接受的。

InnoDB刷脏页的控制策略

设置主机IO能力

InnoDB所在主机的IO能力可让InnoDB知道刷脏页的最大速率

通过innodb_io_capacity参数得知你的磁盘能力

建议将该参数设置成磁盘的IOPS。

磁盘的IOPS可以通过fio这个工具来测试,下面的语句是我用来测试磁盘随机读写的命令:

1
2
> 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参数,而导致的性能问题也比比皆是

通过正确设置该参数,才能让InnoDB以最大效率调用起来

策略考虑因素

  • 脏页比例
  • redo log写盘速度

InnoDB会根据这两个因素先单独算出两个数字。

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

InnoDB会根据当前的脏页比例(假设为M),算出一个范围在0到100之间的数字,这里为F1(M)。

InnoDB每次写入的日志都有一个序号,当前写入的序号跟checkpoint对应的序号之间的差值,我们假设为N。

InnoDB会根据这个N算出一个范围在0到100之间的数字,这个计算公式可以记为F2(N)。

然后,根据上述算得的F1(M)和F2(N)两个值,取其中较大的值记为R,之后引擎就可以按照innodb_io_capacity定义的能力乘以R%来控制刷脏页的速度。

要点

合理地设置innodb_io_capacity的值,并且平时要多关注脏页比例,不要让它经常接近75%

其中,脏页比例是通过`Innodb_buffer_pool_pages_dirty/Innodb_buffer_pool_pages_total得到的

补充拓展:MySQL的邻居策略

一旦一个查询请求需要在执行过程中先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了。

问题

一个内存配置为128GB、innodb_io_capacity设置为20000的大规格实例,正常会建议你将redo log设置成4个1GB的文件。

但如果你在配置的时候不慎将redo log设置成了1个100M的文件,会发生什么情况呢?又为什么会出现这样的情况呢?

参考

每次事务提交都要写redo log,如果设置太小,很快就会被写满即这个“环”将很快被写满,write pos一直追着CP。

这时候系统不得不停止所有更新,去推进checkpoint。

这时,你看到的现象就是磁盘压力很小,但是数据库出现间歇性的性能下跌。

CATALOG
  1. 1. MySQL-脏页相关笔记
    1. 1.1. 引发flush过程
    2. 1.2. InnoDB刷脏页的控制策略
      1. 1.2.1. 设置主机IO能力
      2. 1.2.2. 策略考虑因素
      3. 1.2.3. 要点
    3. 1.3. 问题
      1. 1.3.1. 参考