美文网首页
【MySQL】12|为什么我的MySQL会“抖”一下

【MySQL】12|为什么我的MySQL会“抖”一下

作者: 学而思之 | 来源:发表于2022-01-18 20:47 被阅读0次

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

1、为什么变慢了

前面我们知道了数据库的WAL机制,InnoDB 在处理更新数据的时候,只做了写日志这一个磁盘操作。这个日志叫做 redo log(重做日志),在更新内存写完redo log 后,就返回给客户端,本次更新成功。

以上操作,只是更新了内存中的数据,那么肯定在某个时机将内存里的数据写入磁盘中。这个操作刷内存数据到磁盘的操作叫做 flush。

当内存数据页和磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”。内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”。

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

回到文章开头的问题,不难想象,平时执行很快的更新操作,其实就是在写内存和日志,而MySQL偶尔“抖”一下的那个瞬间,可能就是在刷脏页(flush)。

引起 flush 操作的可能原因:

  1. InnoDB 的redo log 写满了,这时候系统会停止所有更新操作,把checkpoint 往前推进,redo log 留出空间可以继续写。
redo log

如上图中的 write pos 要追上 check point。

  1. 内存不足时。当需要新的内存页,而内存不够用的时候,就需要淘汰一些数据页,空出内存给别的数据页使用。如果淘汰的是“脏页”,就要先将脏页写到磁盘。
  2. 系统空闲时。MySQL认为空闲的时候,就会找机会刷一点“脏页”。
  3. MySQL正常关闭的时候,这时候会把内存的脏页都flush到磁盘上。

上面四种场景对性能的影响:

第一种:

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

第二种:

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

  1. 还没有使用的
  2. 使用了并且是干净页
  3. 使用了并且是脏页

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

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

第三种:

属于MySQL空闲时的操作,系统没有压力。

第四种:

数据库正常关闭,不会关注性能问题。


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

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

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

2、InnoDB 刷脏页的控制策略

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

这就要用到innodb_io_capacity这个参数了,它会告诉InnoDB 你的磁盘能力。这个值建议设置成磁盘的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参数,而导致的性能问题也比比皆是。

现在InnoDB知道了主机的IO能力,我们再看看InnoDB怎么控制引擎按照“全力”的百分比来刷脏页。如果刷太慢,会导致内存脏页太多,其次是redo log写满。如果刷太快,WAL机制就体现不出来优势。

所以,InnoDB 的刷盘速度要参考两个因素:一是脏页比例,二是 redo log 写盘速度。

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

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

InnoDB 每次写入的日志都有一个序号,当前写入的序号跟 checkpoint 对应的序号之间的差值,我们假设为N。InnoDB会根据这个N算出一个范围在 0 到 100之间的数字,这个计算公式可以记为F2(N),N越大,算出来的值就越大。

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

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

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

其中脏页比例可以通过脏页数/总页数来计算。下面是sql:

select VARIABLE_VALUE into @a from performance_schema.global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_dirty'; 
select VARIABLE_VALUE into @b from performance_schema.global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_total'; 
select @a/@b;

另外,说一个有趣的策略。

一旦一个查询请求需要在执行过程中先flush掉一个脏页时,这个查询就可能比平时慢了。

而MySQL中的一个机制,可能让它更慢:在准备刷一个脏页的时候,如果这个数据页旁边的数据页刚好是脏页,就会把这个“邻居”页带着一起刷掉;而且这个“邻居”还会拖它的邻居下水,会一直蔓延下去。

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

在机械硬盘时代,这个参数是有意义的,可以减少很多随机IO。但是在SSD时代,建议设置 innodb_flush_neighbors 的值为0。

在MySQL 8.0 中,innodb_flush_neighbors参数的默认值已经是0了。

相关文章

网友评论

      本文标题:【MySQL】12|为什么我的MySQL会“抖”一下

      本文链接:https://www.haomeiwen.com/subject/ethlhrtx.html