MySQL“抖”了一下,其实是指一条SQL语句在正常情况下执行很快,但有时不知道为什么,突然变得很慢,而且这种场景很难复现,随机出现,并且持续时间短。

变慢的原因

执行SQL的时候,对于更新操作,就是把内存中的数据写入磁盘,术语叫做flush。

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

平常执行得很快,只是在写内存和日志,当它“抖一下”的瞬间,可能是在flush脏页。

什么情况会flush ?

  1. InnoDB的redo log写满了。回忆一下,redo log是一个循环结构,当它记录都放满时,就会停止更新操作,然后把部分脏页flush磁盘上,腾出空间后才能继续写。

  2. 系统内存不足,而又需要新数据页时,会淘汰部分数据页。如果淘汰的是脏页,就要将它写入磁盘。

    • 能否直接淘汰脏页,当下次请求时用redo log写入正确的数据 ?

      可以是可以,但是从性能方面考虑,这样做不太好。如果刷脏页后一定会写入磁盘,那么就保证了每个数据页会有两种状态:

      1. 在内存中,内存的就是正确的结果,可以直接返回。
      2. 不在内存中,磁盘上的文件是正确的结果,读入内存后返回。
  3. MySQL认为系统空闲时,自动刷脏页。

  4. MySQL正常关闭时,刷脏页。

flush对性能的影响

分析一下上面的四种情况。

第三种,空闲时操作,系统没啥压力。

第四种,都结束了,更不会关注性能。因此注意第一第二种。

第一种,这种情况是要避免的,因为出现这种情况,更新就停了,所有更新都要停止,直到刷完脏页,有多余的空间为止。

第二种,对于内存不够了的情况,其实是很常见的。InnoDB使用缓冲池(buffer pool)管理内存,缓冲池的内存页有三种状态:

  1. 未使用的
  2. 使用了并且是干净页
  3. 使用了并且是脏页

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

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

因此,刷脏页是常态。但出现这两种情况,是明显影响性能的:一个查询要淘汰的脏页太多,导致查询的时间变长;日志写满,更新阻塞,写性能=0。

如何避免呢?

InnoDB刷脏页的控制策略

首先,告诉InnoDB主机的IO能力,设置innodb_io_capacity参数。建议设置成磁盘的IOPS,这样InnoDB才知道需要全力刷脏页时,可以刷多块。

然后,还可以参考这两个因素 :脏页比例和redo log写盘速度

这里面有个计算方式,可以控制它刷脏页的速度,以后再补充。

平时要多关注脏页比例,不要让它经常接近75%。


还有一个比较有趣的参数,innodb_flush_neighbors,它有一个“连坐机制”可能让你的查询因为flush变得“更”慢。

如果一个查询需要flush一个脏页,然后它发现这个数据页的旁边数据页刚好是脏页,会顺带把它也一起flush,而且邻居的邻居也会继续蔓延!这种优化在机械硬盘时代是很有意义的,连续存储可以减少很多随机IO,提高性能,但是现在都用SSD了,这类IOPS比较高,也不会在意这个了。因此,在8.0后,它的默认值是0,就是不开启,只刷自己。