数据库抖动

数据库正常读取,偶尔出现读取变慢的情况,然后又恢复了。这可能是 MySQL 在将 redo log flush 到磁盘中。

其中数据库刷脏页的情况有以下四种:

  1. redolog 环形,写满了,写性能跌为 0,要 flush 脏页,影响 IO。(敏感业务不能接受)
  2. 查询时内存不够了,需要淘汰内存中的一些数据页。而如果被淘汰的是脏页,那就需要 flush。
  3. 数据库认为空闲的时候,会进行刷脏页。
  4. MySQL 准备关闭的时候。

需要控制 InnoDB 脏页比例,避免出现上面 1、2 情况。

InnoDB 刷脏页的控制策略

配置 InnoDB 刷脏页的速度 —— innodb_io_capacity

这个参数会告诉 InnoDB 磁盘能力如何,这个值建议设置为磁盘的 IOPS。磁盘 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

不合理设置的话,如 SSD 磁盘但是 innodb_io_capacity 设置的很低,那么 InnoDB 认为这个系统的能力就这么差,所以刷脏页刷得特别慢,甚至比脏页生成的速度还慢,这样就造成了脏页累积,影响了查询和更新性能。

配置 InnoDB 脏页比例上限 —— innodb_max_dirty_pages_pct

当脏页比例达到设置的上限后,就开始进行刷盘操作,该比例默认是 75%。根据比例,InnoDB 会得到一个 0-100 的数字,记作 F1(N)。

除此之外,InnoDB 每次写入的日志都有一个序号,当前写入的序号跟 checkpoint 对应的序号之间的差值,我们假设为 N。InnoDB 会根据这个 N 算出一个范围在 0 到 100 之间的数字,这个计算公式可以记为 F2(N)。F2(N) 算法比较复杂,你只要知道 N 越大,算出来的值越大就好了。

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

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

其中,脏页比例是通过 Innodb_buffer_pool_pages_dirty/Innodb_buffer_pool_pages_total 得到的,具体的命令参考下面的代码:

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

InnoDB 连坐刷脏页

当刷脏页的时候,若该数据页旁边的数据页也刚好是脏页,那么会将“邻居”一起刷掉。如果这个逻辑还可以继续,会进一步蔓延。

这种机制,在 InnoDB 中通过 innodb_flush_neighbors 参数来进行控制。值为 1 代表开启,值为 0 代表关闭。在 MySQL 8.0 中,默认为 0.

找“邻居”这个优化在机械硬盘时代是很有意义的,可以减少很多随机 IO。机械硬盘的随机 IOPS 一般只有几百,相同的逻辑操作减少随机 IO 就意味着系统性能的大幅度提升。

对于 SSD 这种 IOPS 比较高的设备,可以将这个参数关闭,从而减少 SQL 语句的响应时间。