Day876.redolog刷盘问题 -MySQL实战

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

redolog刷盘问题

Hi我是阿昌今天学习记录的是关于redolog刷盘问题的内容。

平时的工作中一条 SQL 语句正常执行的时候特别快但是有时也不知道怎么回事它就会变得特别慢并且这样的场景很难复现它不只随机而且持续时间还很短。

看上去这就像是数据库“抖”了一下


一、你的 SQL 语句为什么变“慢”了

binlog&redoLogWAL 机制

InnoDB 在处理更新语句的时候只做了写日志这一个磁盘操作。这个日志叫作 redo log重做日志也就是《孔乙己》里咸亨酒店掌柜用来记账的粉板在更新内存写完 redo log 后就返回给客户端本次更新成功。

做下类比的话掌柜记账的账本是数据文件记账用的粉板是日志文件redo log掌柜的记忆就是内存。

掌柜总要找时间把账本更新一下这对应的就是把内存里的数据写入磁盘的过程术语就是 flush

在这个 flush 操作执行之前孔乙己的赊账总额其实跟掌柜手中账本里面的记录是不一致的。

因为孔乙己今天的赊账金额还只在粉板上而账本里的记录是老的还没把今天的赊账算进去。

当内存数据页跟磁盘数据页内容不一致的时候称这个内存页为“脏页”。

内存数据写入到磁盘后内存和磁盘上的数据页的内容就一致了称为“干净页”。

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

在这个例子里内存对应的就是掌柜的记忆。

接下来用一个示意图来展示一下“孔乙己赊账”的整个操作过程。

假设原来孔乙己欠账 10 文这次又要赊 9 文。

图 1 “孔乙己赊账”更新和 flush 过程

平时执行很快的更新操作其实就是在写内存和日志而 MySQL 偶尔“抖”一下的那个瞬间可能就是在刷脏页flush

那么什么情况会引发数据库的 flush 过程呢

用咸亨酒店掌柜的这个例子想一想

掌柜在什么情况下会把粉板上的赊账记录改到账本上

  • 第一种场景是粉板满了记不下了。这时候如果再有人来赊账掌柜就只得放下手里的活儿将粉板上的记录擦掉一些留出空位以便继续记账。当然在擦掉之前他必须先将正确的账目记录到账本中才行。这个场景对应的就是 InnoDB 的 redo log 写满了。这时候系统会停止所有更新操作把 checkpoint 往前推进redo log 留出空间可以继续写。redo log 的示意图这里我改成环形便于理解。

图 2 redo log 状态图

checkpoint 可不是随便往前修改一下位置就可以的。比如图 2 中把 checkpoint 位置从 CP 推进到 CP’就需要将两个点之间的日志浅绿色部分对应的所有脏页都 flush 到磁盘上。之后图中从 write pos 到 CP’之间就是可以再写入的 redo log 的区域。

  • 第二种场景是这一天生意太好要记住的事情太多掌柜发现自己快记不住了赶紧找出账本把孔乙己这笔账先加进去。这种场景对应的就是系统内存不足。当需要新的内存页而内存不够用的时候就要淘汰一些数据页空出内存给别的数据页使用。如果淘汰的是“脏页”就要先将脏页写到磁盘。这时候难道不能直接把内存淘汰掉下次需要请求的时候从磁盘读入数据页然后拿 redo log 出来应用不就行了这里其实是从性能考虑的。如果刷脏页一定会写盘就保证了每个数据页有两种状态

    • 一种是内存里存在内存里就肯定是正确的结果直接返回
    • 另一种是内存里没有数据就可以肯定数据文件上是正确的结果读入内存后返回。这样的效率最高。
  • 第三种场景是生意不忙的时候或者打烊之后。这时候柜台没事掌柜闲着也是闲着不如更新账本。这种场景对应的就是 MySQL 认为系统“空闲”的时候。当然MySQL“这家酒店”的生意好起来可是会很快就能把粉板记满的所以“掌柜”要合理地安排时间即使是“生意好”的时候也要见缝插针地找时间只要有机会就刷一点“脏页”。

  • 第四种场景是年底了咸亨酒店要关门几天需要把账结清一下。这时候掌柜要把所有账都记到账本上这样过完年重新开张的时候就能就着账本明确账目情况了。这种场景对应的就是 MySQL 正常关闭的情况。这时候MySQL 会把内存的脏页都 flush 到磁盘上这样下次 MySQL 启动的时候就可以直接从磁盘上读数据启动速度会很快。


上面四种场景对性能的影响。其中第三种情况是属于 MySQL 空闲时的操作这时系统没什么压力而第四种场景是数据库本来就要关闭了。

这两种情况下不会太关注“性能”问题。所以这里主要来分析一下前两种场景下的性能问题。

第一种是“redo log 写满了要 flush 脏页”这种情况是 InnoDB 要尽量避免的。因为出现这种情况的时候整个系统就不能再接受更新了所有的更新都必须堵住。如果你从监控上看这时候更新数会跌为 0。

第二种是“内存不够用了要先将脏页写到磁盘”这种情况其实是常态。InnoDB 用缓冲池buffer pool管理内存缓冲池中的内存页有三种状态

  • 第一种是还没有使用的
  • 第二种是使用了并且是干净页
  • 第三种是使用了并且是脏页。

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

而当要读入的数据页没有在内存的时候就必须到缓冲池中申请一个数据页。

这时候只能把最久不使用的数据页从内存中淘汰掉

如果要淘汰的是一个干净页就直接释放出来复用

但如果是脏页呢就必须将脏页先刷到磁盘变成干净页后才能复用。


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

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

所以InnoDB 需要有控制脏页比例的机制来尽量避免上面的这两种情况。


二、InnoDB 刷脏页的控制策略

InnoDB 脏页的控制策略以及和这些策略相关的参数。

首先要正确地告诉 InnoDB 所在主机的 IO 能力这样 InnoDB 才能知道需要全力刷脏页的时候可以刷多快。

这就要用到 innodb_io_capacity 这个参数了它会告诉 InnoDB 你的磁盘能力。

这个值建议设置成磁盘的 IOPS。磁盘的 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 

其实因为没能正确地设置 innodb_io_capacity 参数而导致的性能问题也比比皆是。

一个库的性能问题说 MySQL 的写入速度很慢TPS 很低但是数据库主机的 IO 压力并不大。经过一番排查发现罪魁祸首就是这个参数的设置出了问题。他的主机磁盘用的是 SSD但是 innodb_io_capacity 的值设置的是 300。于是InnoDB 认为这个系统的能力就这么差所以刷脏页刷得特别慢甚至比脏页生成的速度还慢这样就造成了脏页累积影响了查询和更新性能。

虽然现在已经定义了“全力刷脏页”的行为但平时总不能一直是全力刷吧

毕竟磁盘能力不能只用来刷脏页还需要服务用户请求。

所以接下来就一起看看 InnoDB 怎么控制引擎按照“全力”的百分比来刷脏页。


根据前面提到的知识点试想一下如果来设计策略控制刷脏页的速度会参考哪些因素呢

这个问题可以这么想如果刷太慢会出现什么情况

首先是内存脏页太多其次是 redo log 写满。所以InnoDB 的刷盘速度就是要参考这两个因素

  • 一个是脏页比例
  • 一个是 redo log 写盘速度

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


参数 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 写入速度算出来的两个值。

图 3 InnoDB 刷脏页速度策略
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;

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


三、总结

利用 WAL 技术数据库将随机写转换成了顺序写大大提升了数据库的性能。

但是由此也带来了内存脏页的问题。脏页会被后台线程自动 flush也会由于数据页淘汰而触发 flush而刷脏页的过程由于会占用资源可能会让你的更新和查询语句的响应时间长一些。

一个内存配置为 128GB、innodb_io_capacity 设置为 20000 的大规格实例正常会建议你将 redo log 设置成 4 个 1GB 的文件。但如果你在配置的时候不慎将 redo log 设置成了 1 个 100M 的文件会发生什么情况呢
又为什么会出现这样的情况呢

内存128G所以内存不是瓶颈 innodb_io_capacity设置成20000系统会认为磁盘IO能力很好

redo log设置成100M太小了redo log很容易写满而导致系统停下来去刷脏页写性能为0的概率变高


  • flush 一般是说刷脏页
  • purge一般是指清undo log,
  • merge一般是指应用change buffer

在这里插入图片描述


阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: mysql

“Day876.redolog刷盘问题 -MySQL实战” 的相关文章