ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

给领导讲解线上MySQL为何频繁“抖擞”,听完立马给我升职了!

2021-04-15 17:57:29  阅读:222  来源: 互联网

标签:刷脏页 flush InnoDB 内存 MySQL 脏页 升职 抖擞


还在摸鱼发文的我,自己之前写的SQL也都测过还是挺快的。突然领导在紧急群里艾特我!

看了下监控图,发现总有一些时刻看起来随机持续时间又短,难以复现。

WAL 机制:InnoDB在处理更新语句时,只做写日志这个磁盘操作(redo log),在更新内存写完redo log后,就返回给客户端,本次更新成功。

想象你是一个店老板:

  • 数据文件 - 用来记账的账本
  • redo log - 记账用的粉板
  • 内存 - 你的记忆

你总要找时间把账本更新下,即把内存里的数据写盘(flush)。在flush前,撕葱的赊账总额,其实跟你手中账本的记录不一致。因为撕葱今天的赊账金额还只在粉板上,而账本里的记录是老的,还没把今天的赊账算进去。

  • 内存数据页磁盘数据页内容不一致,该内存页为“脏页”
  • 内存数据写盘后,内存数据页磁盘数据页内容一致了,称为“干净页”

无论脏页 or 干净页,都指内存数据页

撕葱赊账示意图

假设原来撕葱欠账10元,这次又要赊9元。

所以平时执行很快的更新操作,其实就是在写内存和日志,而MySQL抖擞地那一下,可能就是在刷脏页(flush)

领导又开始懵逼了

那何时会触发MySQL的flush呀?
想想店老板何时会把赊账记录落实到账本?

1 redo log写满

粉板写满了,这时再有更多的撕葱来赊账,老板必须放下手活,将粉板上的记录擦掉一些,腾空间以继续记账。当然了,擦掉前也必须先将正确账目记录到账本。
用技术话语描述,就是InnoDB的redo log写满了。这时系统会停止所有更新操作,把checkpoint往前推进,redo log留出空间可以继续写。

  • redo log状态图

checkpoint 不是随便往前修改一下位置的。比如上图,把checkpoint位置从CP ~ CP’,就需要将两个点之间的日志(浅绿色部分),对应的所有脏页都flush。之后,write pos ~ CP’就可再写redo log。

2 系统内存不足

可以这么理解,这天生意太好,要记的事太多,老板发现自己快记不住了,赶紧找出账本把撕葱这笔账先加进去。
当需要新的内存页,而内存不够用的时候,就要淘汰一些数据页,空出内存给别的数据页用。若淘汰的是“脏页”,就要先将脏页写到磁盘。
领导又开始好奇了

这时难道不能直接把内存淘汰掉,下次需要请求时,从磁盘读入数据页,然后拿redo log出来应用不就行了?
这里其实是从性能考虑。若刷脏页一定会写盘,就保证了每个数据页有两种状态:

  1. 内存里存在,内存里就肯定是正确的结果,直接返回
  2. 内存里无数据,就可以肯定数据文件上是正确的结果,读入内存后返回。
    这样的效率最高。

3 MySQL认为系统“空闲”时

生意不忙时自然没啥忙的,店老板也闲着,不如更新下账本。
MySQL空闲机会总是少的,可能很快把日志写满,所以MySQL要合理安排时间,即使很忙,也要见缝插针地找时间,只要有机会就刷一点“脏页”。

4 MySQL正常关闭

年底要关门回家过年了,清算账目。
这时候掌柜要把所有账都记到账本上,这样过完年重新开张的时候,就能就着账本明确账目情况了。
这时MySQL会把内存的脏页都flush,下次启动时,就能直接从磁盘读数据,启动速度会很快。

领导开始犯难了,这么多场景都会触发,它们会对线上稳定性有啥影响吗?

各场景的性能开销

第3种MySQL空闲时的操作,系统本就无压力,第4种MySQL还本来就要关闭了。所以这两种情况你也都不会去关注性能。

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

第2种:“内存不够了,要先将脏页flush”,这种情况其实就是常态。InnoDB用缓冲池(buffer pool)管理内存,缓冲池中的内存页有如下状态

  • 还没有使用的
  • 使用了并且是干净页
  • 使用了并且是脏页。

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

当要读入的数据页不在内存时,就必须到缓冲池中申请一个数据页。这时只能把 最久不使用 的数据页从内存中淘汰,视淘汰的对象不同:

  • 干净页
    直接释放出来复用
  • 脏页
    必须将脏页先刷盘,变干净后才能复用

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

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

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

  • 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机制后续需要的刷脏页操作和执行时机。利用WAL技术,数据库将随机写转换成了顺序写,大大提升了数据库的性能。

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

标签:刷脏页,flush,InnoDB,内存,MySQL,脏页,升职,抖擞
来源: https://blog.csdn.net/qq_33589510/article/details/115707006

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有