mysql学习 实践(4) mysql 刷脏页flash
总结
1、MySQL抖一下是什么意思?
答:抖我认为就是不稳定的意思,一个SQL语句平时速度都挺快的,偶尔会慢一下且没啥规律,就是抖啦!
2、MySQL为啥会抖一下?
答:因为运行的不正常了,或者不稳定了,需要花费更多的资源处理别的事情,会使SQL语句的执行效率明显变慢。针对innoDB导致MySQL抖的原因,主要是InnoDB 会在后台刷脏页,而刷脏页的过程是要将内存页写入磁盘。所以,无论是你的查询语句在需要内存的时候可能要求淘汰一个脏页,还是由于刷脏页的逻辑会占用 IO 资源并可能影响到了你的更新语句,都可能是造成你从业务端感知MySQL“抖”了一下的原因。
3、脏页和干净页的定义是什么?
答:当内存数据页跟磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”。
按照这个定义感觉脏页是不可避免的,写的时候总会先写内存再写磁盘和有没有用WAL没啥关系?
内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”。
4、脏页是咋产生的?
答:因为使用了WAL技术,这个技术会把数据库的随机写转化为顺序写,但副作用就是会产生脏页。
5、啥是随机写?为啥那么耗性能?啥是顺序写?
答:随机写我的理解是,这次写磁盘的那个扇区和上一次没啥关系,需要重新定位位置,机械运动是很慢的,即使不是机械运动重新定位写磁盘的位置也是很耗时的。
顺序写我的理解是,这次写磁盘那个扇区就在上一次的下一个位置,不需要重新定位写磁盘的位置速度当然会快一些。
6、WAL怎么把随机写转化为顺序写的?
答:写redolog是顺序写的,先写redolog等合适的时候再写磁盘,间接的将随机写变成了顺序写,性能确实会提高不少。
自己理解:本来是系统应该直接更新磁盘上的数据,因为每次位置不一样,因此就是随机写;使用WAl技术以后,不写磁盘,而是先写日志。在写redo log日志的时候,其实是不停的在一个日志文件末尾追加日志的,这就是磁盘顺序写。因此也就间接的将随机写变成了顺序写。
7、引发数据库flush脏页的四种典型场景各是什么?对mysql性能的影响各是怎样的? 缓存池中的内存页有哪三种状态? 哪两种刷脏页的情况会比较影响性能?
答:四种场景如下:
- 第一种场景是,粉板满了,记不下了。这时候如果再有人来赊账,掌柜就只得放下手里的活儿,将粉板上的记录擦掉一些,留出空位以便继续记账。当然在擦掉之前,他必须先将正确的账目记录到账本中才行。
这个场景,对应的就是 InnoDB 的 redo log 写满了。这时候系统会停止所有更新操作,把 checkpoint 往前推进,redo log 留出空间可以继续写。画了一个 redo log 的示意图,这里我改成环形,便于大家理解。
图 2 redo log 状态图
- 第二种场景是,这一天生意太好,要记住的事情太多,掌柜发现自己快记不住了,赶紧找出账本把孔乙己这笔账先加进去。
这种场景,对应的就是系统内存不足。当需要新的内存页,而内存不够用的时候,就要淘汰一些数据页,空出内存给别的数据页使用。如果淘汰的是“脏页”,就要先将脏页写到磁盘。
你一定会说,这时候难道不能直接把内存淘汰掉,下次需要请求的时候,从磁盘读入数据页,然后拿 redo log 出来应用不就行了?这里其实是从性能考虑的。如果刷脏页一定会写盘,就保证了每个数据页有两种状态:
-
- 一种是内存里存在,内存里就肯定是正确的结果,直接返回;
- 另一种是内存里没有数据,就可以肯定数据文件上是正确的结果,读入内存后返回。这样的效率最高。
- 第三种场景是,生意不忙的时候,或者打烊之后。这时候柜台没事,掌柜闲着也是闲着,不如更新账本。
这种场景,对应的就是 MySQL 认为系统“空闲”的时候。当然,MySQL“这家酒店”的生意好起来可是会很快就能把粉板记满的,所以“掌柜”要合理地安排时间,即使是“生意好”的时候,也要见缝插针地找时间,只要有机会就刷一点“脏页”。
- 第四种场景是,年底了咸亨酒店要关门几天,需要把账结清一下。这时候掌柜要把所有账都记到账本上,这样过完年重新开张的时候,就能就着账本明确账目情况了。
这种场景,对应的就是 MySQL 正常关闭的情况。这时候,MySQL 会把内存的脏页都 flush 到磁盘上,这样下次 MySQL 启动的时候,就可以直接从磁盘上读数据,启动速度会很快。
上面四种场景对性能的影响如下:
其中,第三种情况是属于 MySQL 空闲时的操作,这时系统没什么压力,而第四种场景是数据库本来就要关闭了。这两种情况下,你不会太关注“性能”问题。所以这里,我们主要来分析一下前两种场景下的性能问题。
第一种是“redo log 写满了,要 flush 脏页”,这种情况是 InnoDB 要尽量避免的。因为出现这种情况的时候,整个系统就不能再接受更新了,所有的更新都必须堵住。如果你从监控上看,这时候更新数会跌为 0。
第二种是“内存不够用了,要先将脏页写到磁盘”,这种情况其实是常态。InnoDB 用缓冲池(buffer pool)管理内存,缓冲池中的内存页有三种状态:
-
- 第一种是,还没有使用的;
- 第二种是,使用了并且是干净页;
- 第三种是,使用了并且是脏页。
InnoDB 的策略是尽量使用内存,因此对于一个长时间运行的库来说,未被使用的页面很少。
而当要读入的数据页没有在内存的时候,就必须到缓冲池中申请一个数据页。这时候只能把最久不使用的数据页从内存中淘汰掉:
- 如果要淘汰的是一个干净页,就直接释放出来复用;
- 但如果是脏页呢,就必须将脏页先刷到磁盘,变成干净页后才能复用。
所以,刷脏页虽然是常态,但是出现以下这两种情况,都是会明显影响性能的:
- 一个查询要淘汰的脏页个数太多,会导致查询的响应时间明显变长;
- 日志写满,更新全部堵住,写性能跌为 0,这种情况对敏感业务来说,是不能接受的。
所以,InnoDB 需要有控制脏页比例的机制,来尽量避免上面的这两种情况。
8、innodb_io_capacity这个参数的作用是什么,这个参数设置错误可能导致什么样的后果,如何正确的设置这个参数?
答:我们需要了解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 就会认为这个系统的能力就这么差,所以刷脏页刷得特别慢,甚至比脏页生成的速度还慢,这样就造成了脏页累积,影响了查询和更新性能。
9、mysql如何通过innodb_io_capacity, 脏页比例(M), 当前日志序号(N)这三个指标来控制以什么样的速度去刷脏页的?
答:虽然我们现在已经定义了“全力刷脏页”的行为,但平时总不能一直是全力刷吧?毕竟磁盘能力不能只用来刷脏页,还需要服务用户请求。所以接下来,我们就一起看看 InnoDB 怎么控制引擎按照“全力”的百分比来刷脏页。
根据我前面提到的知识点,试想一下,如果你来设计策略控制刷脏页的速度,会参考哪些因素呢?
这个问题可以这么想,如果刷太慢,会出现什么情况?首先是内存脏页太多,其次是 redo log 写满。
所以,InnoDB 的刷盘速度就是要参考这两个因素:一个是脏页比例,一个是 redo log 写盘速度。
InnoDB 会根据这两个因素先单独算出两个数字。
参数 innodb_max_dirty_pages_pct 是脏页比例上限,默认值是 75%。InnoDB 会根据当前的脏页比例(假设为 M),算出一个范围在 0 到 100 之间的数字,计算这个数字的伪代码类似这样:
1 F1(M) 2 { 3 if M>=innodb_max_dirty_pages_pct then 4 return 100; 5 return 100*M/innodb_max_dirty_pages_pct; 6 }
InnoDB 每次写入的日志都有一个序号,当前写入的序号跟 checkpoint 对应的序号之间的差值,我们假设为 N。InnoDB 会根据这个 N 算出一个范围在 0 到 100 之间的数字,这个计算公式可以记为 F2(N)。F2(N) 算法比较复杂,你只要知道 N 越大,算出来的值越大就好了。
然后,根据上述算得的 F1(M) 和 F2(N) 两个值,取其中较大的值记为 R,之后引擎就可以按照 innodb_io_capacity 定义的能力乘以 R% 来控制刷脏页的速度。
上述的计算流程比较抽象,不容易理解,所以我画了一个简单的流程图。图中的 F1、F2 就是上面我们通过脏页比例和 redo log 写入速度算出来的两个值。
现在你知道了,InnoDB 会在后台刷脏页,而刷脏页的过程是要将内存页写入磁盘。所以,无论是你的查询语句在需要内存的时候可能要求淘汰一个脏页,还是由于刷脏页的逻辑会占用 IO 资源并可能影响到了你的更新语句,都可能是造成你从业务端感知到 MySQL“抖”了一下的原因。
要尽量避免这种情况,你就要合理地设置 innodb_io_capacity 的值,并且平时要多关注脏页比例,不要让它经常接近 75%。
其中,脏页比例是通过 Innodb_buffer_pool_pages_dirty/Innodb_buffer_pool_pages_total 得到的,具体的命令参考下面的代码:
1 mysql> select VARIABLE_VALUE into @a from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_dirty'; 2 select VARIABLE_VALUE into @b from global_status where VARIABLE_NAME = 'Innodb_buffer_pool_pages_total'; 3 select @a/@b;
10、innodb_flush_neighbor这个参数表示什么意思,应该如何设置?
答:一旦一个查询请求需要在执行过程中先 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 了。
思考
1、当内存不够用了,要将脏页写到磁盘,会有一个数据页淘汰机制(最久不使用),假设淘汰的是脏页,则此时脏页所对应的redo log的位置是随机的,当有多个不同的脏页需要刷,则对应的redo log可能在不同的位置,这样就需要把redo log的多个不同位置刷掉,这样对于redo log的处理不是就会很麻烦吗?(合并间隙,移动位置?)
另外,redo log的优势在于将磁盘随机写转换成了顺序写,如果需要将redo log的不同部分刷掉(刷脏页),不是就在redo log里随机读写了么?
答:其实由于淘汰的时候,刷脏页过程不用动redo log文件的。
这个有个额外的保证,是redo log在“重放”的时候,如果一个数据页已经是刷过的,会识别出来并跳过。
innodb是如何知道一个页是不是脏页的?
每个数据页头部有LSN,8字节,每次修改都会变大。
对比这个LSN跟checkpoint 的LSN,比checkpoint小的一定是干净页
大致过程是:buffer pool里维护着一个脏页列表,假设现在redo log 的 checkpoint 记录的 LSN 为 10,现在内存中的一干净页有修改,修改后该页的LSN为12,大于 checkpoint 的LSN,则在写redo log的同时该页也会被标记为脏页记录到脏页列表中,现在内存不足,该页需要被淘汰掉,该页会被刷到磁盘,磁盘中该页的LSN为12,该页也从脏页列表中移除,现在redo log 需要往前推进checkpoint,到LSN为12的这条log时,发现内存中的脏页列表里没有该页,且磁盘上该页的LSN也已经为12,则该页已刷脏,已为干净页,跳过
2、flush和purge是不是还是有区别的?flush主要指刷新脏页,和clean进程相关?而purge是清理不再被使用的undo信息。
答:对,
flush 一般是说刷脏页,
purge一般是指清undo log,
merge一般是指应用change buffer
3、那个说fio命令会破坏硬盘的兄弟,是没用对命令。估计把-filename=/dev/sdb1 。。。这个的意思是从 分区 sdb1 的第一个扇区开始写入随机数据,去判断这个磁盘的写入速度。如果指定路径+文件名就不会出这事了~比如老师给的例子~
答:写文章的时候,作者还故意用变量,这样直接拷贝会出错,然后自己再写个路径,已经考虑了安全了😓