为何个人MySQL会抖一下

 

一、概念

平时的工做中,不知道有没有遇到过这样的场景,一条SQL语句,正常执行的时候特别快,可是有时也不知道怎么回事,它就会变得特别慢,而且这样的场景很难复现,它不仅随机,并且持续时间还很短。mysql

 

 

二、SQL语句为何会变慢了

如今你知道了,InnoDB在处理更新语句的时候,只作了写日志这一个磁盘操做。这个日志叫做redo log(重作日志),也就是《孔乙己》里咸亨酒店掌柜用来记帐的粉板,在更新内存写完redo log后,就返回给客户端,本次更新成功。算法

 

作下类比的话,掌柜记帐的帐本是数据文件,记帐用的粉板是日志文件(redo log),掌柜的记忆就是内存。sql

 

掌柜总要找时间把帐本更新一下,这对应的就是把内存里的数据写入磁盘的过程,术语就是flush。在这个flush操做执行以前,孔乙己的赊帐总额,其实跟掌柜手中帐本里面的记录是不一致的。由于孔乙己今天的赊帐金额还只在粉板上,而帐本里的记录是老的,还没把今天的赊帐算进去。数据库

 

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

 

不管是脏页仍是干净页,都在内存中。在这个例子里,内存对应的就是掌柜的记忆。性能

 

 

 

平时执行很快的更新操做,其实就是在写内存和日志,而MySQL偶尔“抖”一下的那个瞬间,可能就是在刷脏页(flush)。测试

 

什么状况才会引起数据库的flush的过程呢优化

 

仍是继续使用孔乙己酒店掌柜的例子,掌柜在什么状况下会把粉板上的赊帐记录改到帐本上?线程

 

         第一种场景是,粉板满了,记不下了。这时候若是再有人来赊帐,掌柜就只得放下手里的活儿,将粉板上的记录擦掉一些,留出空位以便继续记帐。固然在擦掉以前,他必须先将正确的帐目记录到帐本中才行。3d

这个场景,对应的就是InnoDB的redo log写满了。这时候系统会中止全部更新操做,把checkpoint往前推动,redo log留出空间能够继续写。

checkpoint可不是随便往前修改一下位置就能够的。好比图2中,把checkpoint位置从CP推动到CP’,就须要将两个点之间的日志(浅绿色部分),对应的全部脏页都flush到磁盘上。以后,图中从write pos到CP’之间就是能够再写入的redo log的区域。

 

第二种场景是,这一天生意太好,要记住的事情太多,掌柜发现本身快记不住了,赶忙找出帐本把这笔帐先加进去。

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

你必定会说,这时候难道不能直接把内存淘汰掉,下次须要请求的时候,从磁盘读入数据页,而后拿redo log出来应用不就好了?这里实际上是从性能考虑的。若是刷脏页必定会写盘,就保证了每一个数据页有两种状态:

 

一种是内存里存在,内存里就确定是正确的结果,直接返回;

另外一种是内存里没有数据,就能够确定数据文件上是正确的结果,读入内存后返回。

这样的效率最高。

 

第三种场景是,生意不忙的时候,或者打烊以后。这时候柜台没事,掌柜闲着也是闲着,不如更新帐本。

这种场景,对应的就是MySQL认为系统“空闲”的时候。固然,MySQL“这家酒店”的生意好起来但是会很快就能把粉板记满的,因此“掌柜”要合理地安排时间,即便是“生意好”的时候,也要见缝插针地找时间,只要有机会就刷一点“脏页”。

 

第四种场景是,年末了咸亨酒店要关门几天,须要把帐结清一下。这时候掌柜要把全部帐都记到帐本上,这样过完年从新开张的时候,就能就着帐本明确帐目状况了。

这种场景,对应的就是MySQL正常关闭的状况。这时候,MySQL会把内存的脏页都flush到磁盘上,这样下次MySQL启动的时候,就能够直接从磁盘上读数据,启动速度会很快。

 

考虑一下以上四种状况对性能的影响。

其中,第三种状况是属于MySQL空闲时的操做,这时系统没什么压力,而第四种场景是数据库原本就要关闭了。这两种状况下,你不会太关注“性能”问题。因此这里,咱们主要来分析一下前两种场景下的性能问题。

 

第一种是“redo log写满了,要flush脏页”,这种状况是InnoDB要尽可能避免的。由于出现这种状况的时候,整个系统就不能再接受更新了,全部的更新都必须堵住。若是你从监控上看,这时候更新数会跌为0。

 

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

 

第一种是,尚未使用的;

第二种是,使用了而且是干净页;

第三种是,使用了而且是脏页。

 

InnoDB的策略是尽可能使用内存,所以对于一个长时间运行的库来讲,未被使用的页面不多。

 

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

 

因此,刷脏页虽然是常态,可是出现如下这两种状况,都是会明显影响性能的:

 

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

 

日志写满,更新所有堵住,写性能跌为0,这种状况对敏感业务来讲,是不能接受的。

 

因此,InnoDB须要有控制脏页比例的机制,来尽可能避免上面的这两种状况。

 

一、innodb刷脏页的控制策略

正确地告诉InnoDB所在主机的IO能力,这样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

 

其实,由于没能正确地设置innodb_io_capacity参数,而致使的性能问题也比比皆是。以前,就曾有其余公司的开发负责人找我看一个库的性能问题,说MySQL的写入速度很慢,TPS很低,可是数据库主机的IO压力并不大。通过一番排查,发现罪魁祸首就是这个参数的设置出了问题。

 

他的主机磁盘用的是SSD,可是innodb_io_capacity的值设置的是300。因而,InnoDB认为这个系统的能力就这么差,因此刷脏页刷得特别慢,甚至比脏页生成的速度还慢,这样就形成了脏页累积,影响了查询和更新性能。

 

虽然咱们如今已经定义了“全力刷脏页”的行为,但平时总不能一直是全力刷吧?毕竟磁盘能力不能只用来刷脏页,还须要服务用户请求。因此接下来,咱们就一块儿看看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)算法比较复杂,你只要知道N越大,算出来的值越大就行了。

 

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

 

如今你知道了,InnoDB会在后台刷脏页,而刷脏页的过程是要将内存页写入磁盘。因此,不管是你的查询语句在须要内存的时候可能要求淘汰一个脏页,仍是因为刷脏页的逻辑会占用IO资源并可能影响到了你的更新语句,均可能是形成你从业务端感知到MySQL“抖”了一下的缘由。

要尽可能避免这种状况,你就要合理地设置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;

 

接下来,咱们再看一个有趣的策略。

一旦一个查询请求须要在执行过程当中先flush掉一个脏页时,这个查询就可能要比平时慢了。而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了。

 

四、小结

利用WAL技术,数据库将随机写转换成了顺序写,大大提高了数据库的性能。

可是,由此也带来了内存脏页的问题。脏页会被后台线程自动flush,也会因为数据页淘汰而触发flush,而刷脏页的过程因为会占用资源,可能会让你的更新和查询语句的响应时间长一些。在文章里,我也给你介绍了控制刷脏页的方法和对应的监控方式。

相关文章
相关标签/搜索