AnthonyZero's Bolg

MySQL笔记-内存数据刷盘机制

MySQL 的刷盘机制

mysql 使用了 WAL 技术,即更新的时候先更新内存中的数据,然后必要的时候再将内存中的数据刷入磁盘。我们把内存中这些被修改过,跟磁盘中的数据页不一致的数据页称为”脏页”. 内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”

平时执行很快的更新操作,其实就是在写内存和日志,而MySQL偶尔“抖”一下的那个瞬间,可能就是在刷脏页(flush)
其中,有四种情况会触发脏页的刷盘:

  1. redo log写满了。这时候系统会停止所有更新操作,把checkpoint往前推进,redo log留出空间可以继续写
  2. 系统内存不足。当需要新的内存页,而内存不够用的时候,就要淘汰一些数据页,空出内存给别的数据页使用。如果淘汰的是“脏页”,就要先将脏页写到磁盘
  3. MySQL认为系统“空闲”的时候。 闲着也是闲着,不如刷脏页
  4. MySQL正常关闭的情况。这时候,MySQL会把内存的脏页都flush到磁盘上,这样下次MySQL启动的时候,就可以直接从磁盘上读数据,启动速度会很快

从这里就可以看出: 数据flush 同步是通过刷新内存page 完成的 不是通过redo log

缓冲池-buffer pool

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

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

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

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

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

  1. 一个查询要淘汰的脏页个数太多,会导致查询的响应时间明显变长;(数据没在内存,进行了淘汰-脏页flush)对应上面的第二种情况
  2. 日志写满,更新全部堵住,写性能跌为0,这种情况对敏感业务来说,是不能接受的。对应上面的第一种情况
InnoDB会在后台刷脏页,而刷脏页的过程是要将内存页数据flush写入磁盘。所以,无论是你的查询语句在需要内存的时候可能要求淘汰一个脏页,还是由于刷脏页的逻辑会占用IO资源并可能影响到了你的更新语句,都可能是造成你从业务端感知到MySQL“抖”了一下的原因

刷脏页的控制策略

首先,我们必须要知道主机磁盘的写入能力有多强,这样 innodb 才可以知道它刷脏页的速度最快应该是多快。

我们可以通过设置 innodb_io_capacity 这个参数来告诉 innodb 磁盘的写入速度。这个参数的值不宜过小,因为这会导致 innodb 错误的估计刷盘速度,最后导致刷脏页的速度跟不上脏页生成的速度。

innodb_io_capacity规定了刷脏页速度的极限,但是实际上磁盘不可能只服务这么一个功能,所以还需要参考 redo log 的刷盘速度和允许的内存中的脏页比例。

参数 innodb_max_dirty_pages_pct 是脏页比例上限,默认值是 75%。innodb 会根据当前的脏页比例(假设为 M),算出一个范围在 0 到 100 之间的数字,这个公式是 F1(M) 。

而每次写入 redo log 的写入点 wp 都会有一个序号,innodb 会根据这个序号和上一次清理日志的界限 cp 之间的差值——我们假设为 N——计算得到一个范围在 0 到 100 之间的数字,这个公式是 F2(N)

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

所以,我们需要关注内存中的脏页比例,让它尽量不要到75%,并且合理的设置 innodb_io_capacity 参数。

另外,由于 mysql 存在这样一个机制:如果要刷盘的脏页相邻的数据页恰好也是脏页,就一起写入磁盘,如果邻居的邻居也是如此。在机械硬盘时代这个策略可以减少随机IO,但是如果使用固态硬盘的话随机IO的性能往往比较高,所以使用这个策略反而拖累了查询性能。因此可以通过 innodb_flush_neighbors关闭这个“连坐”的策略。

答疑

Question:

  1. “内存不够用了,要先将脏页写到磁盘“redo log对应的空间会释放嘛?“redo log 写满了,要 flush 脏页” 那么对应的内存页会释放吗?
  2. 将脏页flush到磁盘上是直接将脏页数据覆盖到对应磁盘上的数据?还是从磁盘上取到数据后取根据redo log记录进行更新后再写入到磁盘?
  3. innodb是如何知道一个页是不是脏页的,是有标记位还是通过redolog的chkeckpoint来确定的?
  4. 请问下访问某条记录时,存储引擎是如何判断这条记录所在的数据页是否在内存当中,这个查内存机制是如何实现的

Answer:

  1. Redolog 的空间是循环使用的,无所谓释放。 对应的内存页会变成干净页。但是等淘汰的时候才会逐出内存
  2. 前者-覆盖
  3. 每个数据页头部有LSN,8字节,每次修改都会变大,对比这个LSN跟checkpoint 的LSN,比checkpoint小的一定是干净页,淘汰的时候,刷脏页过程不用动redo log文件的. 是redo log在“重放”的时候,如果一个数据页已经是刷过的,会识别出来并跳过
  4. 每个页面有编号的。拿着编号去内存看,没有,就去磁盘