暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MySQL学习之flush(刷脏页)

白砂 2021-04-12
5963

在开发的过程中,我有时会遇到一种情况,一条SQL语句,正常执行的时候特别快,但是有时不知道是为什么,就会卡几秒钟,甚至几十秒钟,但是等等要想在复现就很难。
现在通过这个flush的学习,我有可能知道原因是什么了。

之前学习日志的时候,知道InnoDB处理更新语句的时候只修改内存数据页(如果这个数据在内存中,直接进行修改,语句更新结束。如果这个数据不在内存中,则写入change buffer中,语句结束)和写redo log 这个磁盘操作,并没有实际的更新磁盘数据页。

1

flush 是什么?


flush 就是 要将 内存里的数据写入磁盘的过程。这是什么意思呢?
举个例子看下,借用书中的案例,我们把掌柜记账的账本是数据文件,记账用的粉板是日志文件(redo log),掌柜的记忆就是内存。
孔乙己在进行赊账的时候,掌柜的账本里的记录和孔乙己赊账的总额是不一致的。因为孔乙己今天的赊的账还在粉板上,而账本里的记录是上一次的记录,还没有统计到账本中。
掌柜总要找时间把账本更新一下,将内存里的数据写入磁盘的过程就是 flush。


2

脏页 和 干净页 是什么?


在刚开始时候,就提到MySQL实际更新的不是磁盘,而是内存。当内存数据页 和 磁盘数据页内容不一致的时候,这个内存页 就为 “脏页”;内存数据页写入磁盘后,内存数据页 和 磁盘数据页内容一致,称之为 “干净页”。 
不管是脏页 还是 干净页 ,他们都在内存中。

这个时候,理解到了更新执行很快的原因是因为什么了,其实就只是再写内存和日志。那MySQL有时候会卡一下是因为什么呢?这个时候可能就是在进行flush。

3

flush (刷脏页)触发场景


1、InnoDB 的 redo log 写满了。

如果说redo log 写满之后,这时候系统的所有更新操作都会进行停止。
redo log 是顺序写的,redo log 的大小是固定的,比如可以一组配置4个文件,每个文件大小是1G,那么这个redo log 可以记录4G。
(写redo log 示意图 --图引自 MySQL45讲)

    write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。

    

    checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。 


    write pos 和 checkpoint 中间空着的部分,就是可以写入的部分,如果write pos 追上了 checkpoint ,代表写满了,就需要停下擦除一部分,把checkpoint推进一下。


(redo log 示意图 --图引自 MySQL45讲)
如果说把checkpoint 从 cp 的位置 移动到 cp‘ 的位置,就需要把中间绿色部分对应的所有脏页都 flush 到磁盘上。

2、系统内存不足时。

当需要新的内存页,而内存又不够用的时候,就需要淘汰一些数据页,空出内存给别的数据页使用。如果淘汰的是脏页,就需要讲将脏页flush到磁盘。

在这里的时候当时我有一个问题,为什么不把脏页数据直接删掉,下次需要的时候,从磁盘读取数据页,然后拿redo log 进行应用就行了?
这里之所以没有删除内存数据页,而是将脏页刷到磁盘里,其实是从性能的角度出发的。为什么这样讲呢,因为如果刷脏页,那么下次从磁盘读出来的就是干净页,以后会省事;如果丢掉的话,下次从磁盘读取出来的还是脏页,需要再刷一次脏页。所以既然避免不了刷脏页,不如早点刷到磁盘,还省去了中间很多的逻辑判定操作 和 少读一次磁盘 io 的操作。

3、系统空闲时,
MySQL 在 系统 空闲的时候会进行刷脏页,当然,在系统繁忙的时候也会见缝插针的做这个事情。

4、MySQL 正常关闭时,也会将 内存中的脏页 都刷到 磁盘上,这样在MySQL 下次启动的时候,就可以直接从磁盘上读取数据了。

在以上的四种情况中,3 和 4 都是对性能不会造成什么影响,因为都是在MySQL空闲时的操作。 
1 是redo log  写满后,所有的更新操作都会进行阻塞,这个时候,系统的更新数就相当于为0,这对于业务来讲,可能无法忍受。
2 是内存不够用。在这里要提到一点就是,MySQL 的核心理念就是 有内存先用内存,包括写数据,log 等等的执行动作,因此InnoDB用缓冲池(buffer pool)管理内存。
 buffer pool 中的内存页 有三种状态:
  • 还没使用的

  • 使用了并且是干净的

  • 使用了并且是脏页

在长时间的运行MySQL后, 很少会出现没有使用的内存页。当要读入的数据页不在内存中的时候,要去缓冲池申请数据页。那么在没有未使用的内存页情况下就只有淘汰最久不用的内存页了。如果淘汰的是干净页,那么直接释放复用即可。但是如果是脏页,就必须刷脏页到磁盘,变为干净页之后才能释放复用。当一次查询要淘汰的脏页太多的时候,就会出现性能问题

4

InnoDB刷脏页策略


在 redo log 满 或者 查询一次从内存淘汰太多脏页的时候会出现性能问题,所以要对脏页的比例进行控制。
innodb_io_capacity 这个参数会告诉 InnoDB 磁盘的IO能力。如果这个参数值设置的太低,那么InnoDB 会认为 磁盘 io 很慢,刷脏页也会很慢,这会导致 redo log 满。这个值建议为磁盘的 IOPS (每秒的读写次数)。

InnoDB 刷盘速度要考虑两个因素;1、脏页比列;2、redo log 写盘速度。
innodb_max_dirty_pages_pct这个参数是脏页比例上限,默认是75%。InnoDB会根据当前脏页比例(假设是M)算出一个值F1(M),算法如下:
    F1(M)
    {
    if M>=innodb_max_dirty_pages_pct then
    return 100;
    return 100*M/innodb_max_dirty_pages_pct;
    }
    InnoDB每次写入日志会有序号,当前写入序号和checkpoint的差值(假设为N),InnoDB会算出一个值F2(N)。这个值在0到100之间,N越大F2(N)越大。
    根据F1(M)和F2(N)两者较大的那个(假设值为R),引擎可以按照innodb_io_capacity定义的能力乘以R%来控制刷脏页的速度。

    (引自 MySQL45讲)

    另外还有一个机制,可能会让查询很慢。MySQL在进行刷脏页的时候,如果要刷的脏页旁边的数据页也是脏页,会把这个“邻居”带着一起刷掉,而且这个逻辑会继续蔓延,继续判断邻居的邻居是不是脏页,如果是,一并刷掉。这个时候可以 把 innodb_flush_neighbors  设置为 0 就可以控制 只刷自己就行了。是不是很有趣。



    如果笔记有什么错误的地方或者哪里有问题的话,麻烦各位指点,给我留言就好

    最后,求关注。每天进步一点点,欢迎关注我的公众号「白砂」

    如果我的文章对你有所帮助,还请帮忙点赞、在看、转发一下,非常感谢!


    点个在看你最好看

    文章转载自白砂,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

    评论