MySQL“抖”了一下,其实是指一条SQL语句在正常情况下执行很快,但有时不知道为什么,突然变得很慢,而且这种场景很难复现,随机出现,并且持续时间短。
变慢的原因
执行SQL的时候,对于更新操作,就是把内存中的数据写入磁盘,术语叫做flush。
当内存数据页和磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”。内存数据写入到磁盘后,内存和磁盘上的数据页内容一致的,它就被称为“干净页”。
平常执行得很快,只是在写内存和日志,当它“抖一下”的瞬间,可能是在flush脏页。
什么情况会flush ?
-
InnoDB的redo log写满了。回忆一下,redo log是一个循环结构,当它记录都放满时,就会停止更新操作,然后把部分脏页flush磁盘上,腾出空间后才能继续写。
-
系统内存不足,而又需要新数据页时,会淘汰部分数据页。如果淘汰的是脏页,就要将它写入磁盘。
-
能否直接淘汰脏页,当下次请求时用redo log写入正确的数据 ?
可以是可以,但是从性能方面考虑,这样做不太好。如果刷脏页后一定会写入磁盘,那么就保证了每个数据页会有两种状态:
- 在内存中,内存的就是正确的结果,可以直接返回。
- 不在内存中,磁盘上的文件是正确的结果,读入内存后返回。
-
-
MySQL认为系统空闲时,自动刷脏页。
-
MySQL正常关闭时,刷脏页。
flush对性能的影响
分析一下上面的四种情况。
第三种,空闲时操作,系统没啥压力。
第四种,都结束了,更不会关注性能。因此注意第一第二种。
第一种,这种情况是要避免的,因为出现这种情况,更新就停了,所有更新都要停止,直到刷完脏页,有多余的空间为止。
第二种,对于内存不够了的情况,其实是很常见的。InnoDB使用缓冲池(buffer pool)管理内存,缓冲池的内存页有三种状态:
- 未使用的
- 使用了并且是干净页
- 使用了并且是脏页
由于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,就是不开启,只刷自己。