在MySQL日志系统文章中我们已经介绍过MySQL中的重做日志redo log,对于更新操作,InnoDB会写redo log,以保证MySQL崩溃恢复。InnoDB在处理更新语句时,更新完内存记完redo log后就返回客户端成功,更新结束。
脏页
此时内存中的数据和磁盘中的数据是不一致的,不一致的这个数据页就被称为“脏页”。
刷脏页(flush)
既然磁盘中的数据和内存中的数据有不一致的,那肯定就涉及到将内存中的数据同步到磁盘中,那这个过程就被称为**“刷脏”**。
刷脏页的时机
MySQL定时刷
MySQL会在自认为系统“空闲”的时候或者当系统更新很频繁,redo log很快就写满的情况下,合理的定时进行刷脏
MySQL内存(buffer pool)不足的时候
当需要将数据页读到内存中时,发现内存不够了,就需要淘汰掉内存中最不经常使用的数据页,空出内存给别的数据页使用,如果淘汰的是“脏页”,就要先把脏页写到磁盘中。
MySQL正常关闭的时候
如果关闭的时候不刷脏,启动的时候就需要去读redo log然后同步数据到磁盘,这样启动速度会变慢。
redo log满了的时候
redo log写满的时候,整个系统就不能再接收更新了,所有的更新必须都阻塞住。这种情况要尽量避免。
刷脏导致的性能问题
- 一个查询要淘汰的脏页个数太多,会导致查询的相应时间明显变长
- 日志写满,更新全部读住,写性能跌为0,这种情况对敏感业务来说,是不能接受的
因此InnoDB要控制脏页比例,来尽量避免这两种情况
刷脏页的控制策略
需要正确高速InnoDB所在主机的IO能力,这样InnoDB才能知道需要全力刷脏页的时候,可以刷多快。
可以通innodb_io_capacity参数来控制InnoDB的刷盘能力,这个值建议设置成磁盘的IOPS,通过fio工具可以测试出磁盘的IOPS,命令如下:
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的默认值为200,正确配置innodb_io_capacity可以发挥机器的性能,错误配置也会导致性能问题,比如使用SSD的硬盘就可以将这个值配大些,但是也不能配置过大,配置过大会导致InnoDB把磁盘的能力全用来刷脏页了,不能服务用户请求。
控制刷脏页速度的因素
如果刷脏页慢,会导致内存脏页太多,其次是redo log写满(因为脏页还没有同步到磁盘,redo log就不能覆写)。
InnoDB的刷盘速度就通过脏页比例和redo log写盘速度来控制的.
为了减少刷脏给业务带来的影响,要合理的设置innodb_io_capacity
的值,并且平时要多关注脏页比例,不要让它经常解决75%。
脏页比例计算:
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;
连带机制
一旦一个查询请求需要在执行过程中刷掉一个脏页时,这个查询就可能要比平时慢了,MySQL中的一个机制可能会让查询更慢。
在准备刷一个脏页的时候,如果这个数据页旁边的数据页刚好是脏页,就会把这个“邻居”也带着一起刷掉,并且这个逻辑会继续蔓延。
通过innodb_flush_neighbors
可以控制这个行为,值为1的时候会有上述的连带机制,MySQL8.0以下默认为1。
建议:
- 当使用的是机械硬盘时,建议开启,这样可以减少很多的随机IO
- 当使用的是固态硬盘这类IOPS比较高的设备时,建议关闭,因为这个时候IOPS往往不是瓶颈,这样可以减少SQL的响应时间