同一个sql,可能平时都执行的很快,但是在某一刻会很慢,但是这样的场景不能稳定复现,而且很快就没了。
InnoDB在处理更新语句的时候,并不是每次都直接写入磁盘的而是将操作记录到redo log中;当磁盘文件数据和内存数据不一致的的情况下,将内存中的数据页称之为 “脏页”,内存中的数据写入到磁盘后,数据都一致了,这个数据页就被称为 “干净页”。将内存中的数据写入到磁盘中的操作,称为flush,类似于change buffer中的merge;
当一个平时很快的sql在某一刻突然变得很慢,可能就是数据库在进行刷脏页的操作。
什么时候会需要刷脏页的操作?
- redo log日志已经满了,记录不了新的操作日志了。这个时候InnoDB会停止所有更新操作,将redo log文件记录的数据应用到磁盘中,从而将check point的位置重置一下,新的日志就从write position位置继续写入。
- 内存已经用满了。新来的查询需要的数据不在当前内存的数据页中,就需要从磁盘去读取数据,并且要将读取到的数据写入到内存中,内存不够用了,这个时候就需要将内存中最久没有使用过的数据页给淘汰掉,腾出来新空间让新数据写入。如果淘汰的数据页是“干净页”,那就直接淘汰掉,不需要刷脏页操作;如果淘汰的是 “脏页”,那就需要先将 “脏页”的数据写入到磁盘后才可以腾出来。(那为什么不直接把当前的内存中的数据页都给淘汰掉,当有查询请求过来的时候,先从磁盘读取数据,读入到内存中,并且拿redo log的日志更新?这样的操作代价可能更大,所以没有采取这么做)。
- 系统空闲的时候
- mysql正常关闭的时候
其中第二种 " 内存不够用了,需要先将脏页写入磁盘 "是常态。InnoDB用缓冲池(buffer pool)管理内存,缓冲池中的内存页有三种状态:
- 未使用过的数据页
- 使用过的干净页
- 使用过的脏页
刷脏页是常态的操作,但是如果出现了这两种情况,都是会明显的影响性能的
- 查询请求中涉及到的脏页很多,刷脏页就会比较慢,导致响应的时间会比较长。
- 新来了一个更新请求,准备将操作记录到日志中,发现日志写满了,新的更新请求就需要日志先写入到磁盘中,然后再记录日志。
InnoDB刷脏页的控制策略
可以通过配置 innodb_io_capacity 参数配置刷脏页的速度,如果配置的很慢的话,就会导致脏页堆积,影响性能。还会导致redo log
写满。因为每次有更新请求进来,都会记录到redo log日志中,但是redo log日志满了的话就需要通过刷脏页来将已经写入到磁盘中的那些数据的日志给覆盖掉,从而腾出空间继续写日志。
参数innodb_max_dirty_pages_pct是脏页比例上限,默认值是75%,InnoDB每次写入的日志都有一个序号,当前写入的序号跟checkpoint对应的序号之间的差值,InnoDB会将这两个值经过一定的计算得出一个数,这个结果数就是用来控制刷脏页的速度。
mysql有一个机制:在准备刷一个脏页的时候,如果这个数据页旁 边的数据页刚好是脏页,就会把这个“邻居”也带着一起刷掉;而且这个把“邻居”拖下水的逻辑还 可以继续蔓延,也就是对于每个邻居数据页,如果跟它相邻的数据页也还是脏页的话,也会被放 到一起刷。
在InnoDB中,innodb_flush_neighbors 参数就是用来控制这个行为的,值为1的时候会有上述 的“连坐”机制,值为0时表示不找邻居,自己刷自己的。找“邻居”这个优化在机械硬盘时代是很有意义的,可以减少很多随机IO。机械硬盘的随机IOPS 一般只有几百,相同的逻辑操作减少随机IO就意味着系统性能的大幅度提升。 而如果使用的是SSD这类IOPS比较高的设备的话,我就建议你把innodb_flush_neighbors的值 设置成0。因为这时候IOPS往往不是瓶颈,而“只刷自己”,就能更快地执行完必要的刷脏页操 作,减少SQL语句响应时间。 在MySQL 8.0中,innodb_flush_neighbors参数的默认值已经是0了。