12 | 为什么我的MySQL会“抖”一下?

有时候一条本应该执行很快的SQL语句不知道怎么回事执行的很慢,并且这样的场景很难复现,这样的问题不只是岁间,而且持续时间很短。看上去,好像就是数据库“抖”了一下。

你的SQL语句为什么变“慢”了

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

这儿有个过程,就是把脏页变成干净页,也就是刷脏页。可能MySQL的“抖”的那一下就是在刷脏页。

 

什么情况会刷脏页?

第一种场景,InnoDB的redo log写满了。这时候系统会停止所有更新操作,把checkpoint往前推进,redo log留出空间可以继续写。

把checkpoint位置从CP推进到CP' ,就需要将两个点之间的日志对应的脏页都flush倒磁盘上。之后,从write pos到CP' 之间就是可以再写入的redo log区域。

第二种场景,系统内存不足。当需要新的内存页,而内存不够用的时候,就要淘汰一些数据页,空出内存给别的数据页使用。如果淘汰的数据页是“脏页”,就要先将脏页写到磁盘。

第三种场景,MySQL认为系统“空闲”的时候,将脏页写入磁盘。

第四种场景,MySQL正常关闭的时候,会刷脏页。

 

以上四种情况对性能的影响。

第三种和第四种场景一般不太会关注性能,所以主要看前两种情况下的性能问题。

第一种情况,redo log写满了,要flush脏页,这种情况是InnoDB要尽量避免的。因为出现这种情况的时候,整个系统就不能再接收更新了,所有的更新都必须堵住。从监控上看,这时候更新数据会跌为0。

第二种情况,内存不够用,杨先将脏页写到磁盘上。这种情况其实是常态。InnoDB用缓冲池(buffer pool)管理内存,缓冲池中的内存页有三种状态:

1、还没有使用;

2、使用了并且是干净页;

3、使用了并且是脏页。

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

 

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

刷脏页虽然是常态,但是出现以下两种情况,都是会明显影响性能的:

1.一个查询要淘汰的脏页个数太多,会导致查询的相应时间明显变长;

2.日志写满,更新全部堵住,写性能跌为0,这种情况对于敏感的业务来说,是不能接受的。

所以InnoDB需要有控制脏页比例的机制,来尽量避免上面这两种情况。

 

InnoDB刷脏页的控制策略

首先要正确的告诉InnoDB所在主机的IO能力,这样InnoDB才能知道需要全力刷脏页的时候,可以刷多快。

这个参数:innodb_io_capacity    这个参数会告诉InnoDB你的磁盘的能力。

这个值建议设置成IOPS。磁盘的IOPS可以通过fio这个工具来测试。

 

设计策略控制刷脏页的速度需要考虑哪些因素?

如果刷太慢,会出现什么情况?首先是内存脏页太多,其实是redo log写满。

所以,InnoDB的刷脏页速度就是要考虑这两个因素:一个是脏页比例,一个是redo log写盘速度。

InnoDB会根据这两个因素先单独算出两个数字。

参数innodb_max_dirty_pages_pct是脏页比例上限,默认值是75%。InnoDB会根据当前的脏页比例(假设为M),算出一个范围在0到100之间的数字,计算这个数字的伪代码类似这样:

F1(M)
{
    if M>=innodb_max_dirty_pages_pct then
        return 100;
    return 100*M/innodb_max_dirty_pages_pct;
}

 

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

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

 

所以业务端感到MySQL“抖”了一下的原因就可能就是后台在刷脏页占用了IO资源而影响了更新语句,查询语句在需要内存的时候可能要淘汰一个脏页。

 

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

 

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

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_flush_neighbors用来控制这个行为,值为1的时候会有上述的“连坐”机制,值为0的时候,不找邻居,自己刷自己的。

 

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

而如果是SSD这类IOPS比较高的设备的话,还是建议将这个行为关掉。因为IOPS往往不是瓶颈,而“只刷自己”,就能更快的执行完必要的脏页操作,减少SQL语句的响应时间。

在MySQL8.0中,改参数的默认值已经是0了。

 

上一篇:11 | 怎么给字符串字段加索引?

下一篇:13 | 为什么表数据删掉一半,表文件大小不变?

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值