• 《MySQL实战45讲》——学习笔记12 “InnoDB刷脏页的控制策略“


    本篇介绍MYSQL InnoDB的WAL机制带来的小问题——利用WAL技术,数据库将随机写转换成了顺序写,大大提升了数据库的性能,但也带来了内存脏页的问题;

    脏页会被后台线程自动flush,也会由于数据页淘汰而触发flush,而刷脏页的过程由于会占用资源,可能会让更新和查询语句的响应时间长一些,表现为像是MySQL"抖了一下";

    本篇的知识点包含flush刷盘、脏页/干净页、flush刷盘时机、flush刷盘对性能的影响、InnoDB刷脏页的控制策略;前置知识点可参考MySQ日志系统

    “脏页”与“干净页”

    InnoDB在处理更新语句的时候,由于WAL机制,只做了写redo log(重做日志)这一个磁盘操作;这个redo log就是粉板记账例子中掌柜用来记账的粉板,在更新内存写完redo log后,就返回给客户端,本次更新成功;

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

    在这个flush操作执行之前,内存数据页跟磁盘数据页内容是不一致的,这时我们称这个内存页为“脏页”;“脏页”不代表它的数据有问题,反而它的数据是正确的

    内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”;不论是脏页还是干净页,都是内存中的数据页;

    在这个粉板记账的例子里,内存对应的就是掌柜的记忆,下面的示意图展示了“孔乙己赊账”的整个操作过程:假设原来孔乙己欠账10文,这次又要赊9文;

    MySQL会“抖”一下?

    平时的工作中,可能遇到过这样的场景:一条SQL更新语句,正常执行的时候特别快,但是有时也不知道怎么回事,它就会变得特别慢,并且这样的场景很难复现,它不只随机,而且持续时间还很短,看上去,这就像是数据库“抖”了一下;

    平时执行很快的更新操作,其实就是在写内存和日志,而MySQL偶尔“抖”一下的那个瞬间,可能就是在执行刷脏页(flush);分析SQL变慢的原因之前,先关注下刷脏页(flush)的时机;

    数据库的flush的时机

    继续用粉板记账这个例子,想一想:掌柜在什么情况下会把粉板上的赊账记录改到账本上?

    场景一:redo log写满了,需要移动check point并flush这段redo log对应的脏页;

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

    redo log的checkpoint推进的示意图如下,把checkpoint位置从CP推进到CP’,就需要将两个点之间(浅绿色部分)的日志对应的所有脏页都flush到磁盘上;之后,图中从write pos到CP’之间就是可以再写入的redolog的区域;

    场景二:内存满了,淘汰的内存数据页刚好是脏页,就需要flush到磁盘;

    这一天生意太好,要记住的事情太多,掌柜发现自己快记不住了,赶紧找出账本把孔乙己这笔账先加进去;这种场景,对应的就是系统内存不足;

    当需要新的内存页,而内存不够用的时候,就要淘汰一些数据页,空出内存给别的数据页使用;如果淘汰的是“脏页”,就要先将脏页写到磁盘;

    这条规则——刷脏页一定会写盘,就保证了每个数据页只有两种状态:一种是内存里存在,内存里就肯定是正确的结果,直接返回;另一种是内存里没有数据,就可以肯定磁盘里是正确的结果,读入内存后返回;这样的效率最高;

    场景三:mysql空闲的时候执行flush,避免redo log写满阻塞写操作和查询时一次淘汰过多脏页影响查询性能的情况;

    当生意不忙的时候,或者打烊之后;这时候柜台没事,掌柜闲着也是闲着,不如更新账本;这种场景,对应的就是MySQL认为系统“空闲”的时候;

    当然,MySQL“这家酒店”的生意好起来的时候可是会很快就能把粉板记满的,所以“掌柜”需要合理地安排时间,即使是“生意好”的时候,也要见缝插针地找时间,只要有机会就刷一点“脏页”;

    场景四: MySQL正常关闭的时候,需要把内存中的数据全部数据flush到磁盘中;

    到年底了,咸亨酒店要关门几天,需要把账结清一下;这时候掌柜要把所有账都记到账本上,这样过完年重新开张的时候,就能就着账本明确账目情况了;这种场景,对应的就是MySQL正常关闭的情况;

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

    数据库的flush对性能的影响

    接下来,分析一下上面四种场景对性能的影响;其中,上面第三种情况是属于MySQL空闲时的操作,这时系统没什么压力,性能影响可以不考虑;而第四种场景是数据库本来就要关闭了,也不用关注“性能”问题;所以这里,我们主要来分析一下前两种场景下的性能问题:

    场景一:“redo log写满了,要flush脏页”,这种情况是InnoDB要尽量避免的

    因为出现这种情况的时候,没有空间写redo log意味着整个系统就不能再接受更新了所有的更新都必须堵住;如果你从监控上看,这时候更新数会跌为0;

    场景二:“内存不够用了,淘汰脏页时,要先将脏页写到磁盘”,这种情况其实是常态;

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

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

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

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

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

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

    以上问题是脏页在内存中的占比太大引起的,或者可以理解是脏页"刷的太慢了导致脏页堆积快"引起的;但是如果脏页在内存的占比太小,那写场景下内存的使用率不高;而刷磁盘flush太快会一定程度影响IO性能,毕竟磁盘能力不能只用来刷脏页,还需要服务用户请求;所以,InnoDB需要有控制脏页比例的机制,来尽量避免上面的这两种情况

    InnoDB刷脏页flush的控制策略

    首先,要正确地告诉InnoDB所在主机的IO能力;

    这样InnoDB才能知道需要全力刷脏页的时候,最多可以刷多快;MySQL中使用innodb_io_capacity 这个参数告诉InnoDB当前磁盘的IO能力,这个值建议设置成磁盘的IOPS(Input/Output Operations Per Second);

    innodb_io_capacity的值需要被正确的设置;——innodb_io_capacity如果设置的太大,可能导致实际的磁盘IO跟不上InnoDB刷脏页的速度,磁盘IO占满,同时由于脏页刷的太快,内存的实际使用率不高;如果设置的太小,可能导致导致 redo log 很快写满,影响MySQL的平均写性能,同时内存中的脏页占比可能过高,一个查询可能要淘汰较多的脏页,会影响查询性能;

    innodb_io_capacity的默认值为200,这个值是针对一般的机械硬盘的,如果主机磁盘用的是SSD固态硬盘,一定要适当调大这个值,一般SSD建议改为20000;

    虽然现在已经定义了“全力刷脏页”的行为,但平时总不能一直是全力刷吧?毕竟磁盘能力不能只用来刷脏页,还需要服务其他的业务如处理服务用户请求,因此还要适当控制对“全力刷脏页”能力的使用占比

    如果脏页刷太慢,可能导致:(1)内存脏页太多,(2)其次是 redo log 写满;所以,InnoDB的刷盘速度就是要参考这两个因素:一个是脏页比例,一个是 redo log 写盘速度;InnoDB 会根据这两个因素先单独算出两个数值,从而确定对“全力刷脏页”能力的使用占比,来得到最终的刷脏页的速度;

    解释MySQL为何会有时候会“抖”一下?

    现在你知道了,InnoDB会在后台刷脏页,而刷脏页的过程是要将内存页写入磁盘;所以,无论是查询语句在需要内存的时候可能要求淘汰一个脏页,还是由于刷脏页的逻辑会占用IO资源并可能影响到了你的更新语句,都可能是造成你从业务端感知到MySQL“抖”了一下的原因

    要尽量避免这种情况,你就要合理地设置innodb_io_capacity的值,并且平时要多关注脏页比例,不要让它经常接近75%;

    刷盘时的"连坐"机制——innodb_flush_neighbors参数

    一旦一个查询请求需要在执行过程中先flush掉一个脏页时,这个查询就可能要比平时慢了;而MySQL中的一个机制,可能让你的查询会更慢:在准备刷一个脏页的时候,如果这个数据页旁边的数据页刚好是脏页,就会把这个“邻居”也带着一起刷掉;而且这个把“邻居”拖下水的逻辑还可以继续蔓延,也就是对于每个邻居数据页,如果跟它相邻的数据页也还是脏页的话,也会被放到一起刷;

    在InnoDB中,innodb_flush_neighbors参数就是用来控制这个行为的,值为1的时候会有上述的“连坐”机制,值为0时表示不找邻居,自己刷自己的;

    为什么要有这种机制呢?找“邻居”这个优化在机械硬盘时代是很有意义的——可以减少很多随机IO

    机械硬盘的随机IOPS一般只有几百,相同的逻辑操作减少随机IO就意味着系统性能的大幅度提升;而如果使用的是SSD这类IOPS比较高的设备的话,我就建议把innodb_flush_neighbors的值设置成0;因为这时候IOPS往往不是瓶颈,而“只刷自己”,就能更快地执行完必要的刷脏页操作,减少SQL语句响应时间;在MySQL8.0中,innodb_flush_neighbors参数的默认值已经是0了;

    思考题

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

    答案:相当于一个高配IO性能很好的机器,redo log 设置太小;会导致redo log很快被写满,write pos一直追着checkpoint跑,这时候系统不得不停止所有更新,去推进 checkpoint;由于主机IO能力很强,checkpoint的推进(刷盘)会很快完成,卡住的写操作又很快可以执行;循环往复,看到的现象就是磁盘IO压力很小,但是数据库出现间歇性的性能下跌

    下篇文章:《MySQL实战45讲》——学习笔记13 “数据删除流程、表空间释放、重建表过程、online DDL“

    本章参考:12 | 为什么我的MySQL会“抖”一下?-极客时间

  • 相关阅读:
    Word的只读模式和限制编辑有区别吗?如何设置和取消?
    干货,深入剖析ReentrantLock源码,推荐收藏
    【计算机网络】TCP连接建立和释放
    【微信小程序】小程序支持的css选择器、小程序自适应单位rpx简介
    【STM32】STM32F103C6T6标准外设库
    Last Week in Milvus
    docker-compose部署Nacos集群
    MySQL数据库技术
    C++学习——构造函数、析构函数
    基于spring boot开发的6个秋招必备项目,搞起来
  • 原文地址:https://blog.csdn.net/minghao0508/article/details/127637525