当内存数据页和磁盘数据页的内容不一致时,那么这个内存页就是 “脏页”。
平时执行很快的更新操作,只是在写内存和 redo log。而偶尔的那一条执行很慢的更新操作,可能就是在刷脏页。
什么情况会引起刷脏页的过程呢?
- InnoDB 的 redo log 写满了,这是系统会停止所有更新操作,把 redo log 的 checkpoint 往前推进。其中 checkpoint 是当前要擦除的位置,也是往前推移并且循环的,擦除记录前要把记录更新到数据文件。当改动 checkpoint 之后,redo log 就可以留出空间继续写数据。
- 如果 MySQL 内存不足,当需要新的内存页,而内存不够用时,就要淘汰一些数据页,空出内存给别的数据页使用。如果淘汰的是 “脏页”,就要先将脏页写到磁盘。
- MySQL 会在系统空闲时,或者定时,将脏页写到磁盘。
- MySQL 正常关闭的情况下,会把内存的脏页刷到磁盘中
第三、四种场景对性能的影响,我们不关注,因为场景本身就是系统空闲时,数据库本来就要关闭时。我们主要关注第一、二种场景。
一、redo log 写满了,要刷脏页
这种情况需要尽量避免。因为出现这种情况时,整个系统就不能再接受更新操作了,所有的更新操作都会被阻塞。
二、内存不够,脏页刷盘
InnoDB 使用内存池管理内存,内存池中的内存页有三种状态:1. 未使用。 2.使用了并且是干净页。3.使用了并且是脏页。
当要读入的数据页没有在内存的时候,就必须到内存池中申请一个数据页。这时候只能把最久不使用的数据页从内存中淘汰掉。如果要淘汰的是一个干净页,就直接释放出来复用;如果是脏页,就必须将脏页先刷到磁盘,变成干净页后才能使用。
刷脏页虽然是比较常见的场景,但是如果出现这两种情况,会明显影响性能:
- 查询要淘汰的脏页个数太多,会导致查询的响应时间明显变长
- 日志写满,更新全被阻塞。对于敏感业务来说,是不能接受的
所以,InnoDB 需要有控制脏页比例机制,来避免这两种情况。
三、InnoDB 刷脏页的控制策略
InnoDB 的刷盘速度主要参考两个因素:一个是脏页比例,一个是 redo log 写盘速度。
脏页比例一般应该小于 75%
。
对于 redo log 的写盘速度,可以设置 innodb_io_capacity
参数,也就是说告诉 InnoDB,当前所在主机的 IO 能力,这样 InnoDB 才能知道需要全力刷脏页的时候,可以刷多快。一般此值设置为 磁盘的 IOPS。磁盘的 IOPS 可以使用 fio 这个工具来测试。
1 | 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 能力较低,所以刷脏页就刷的比较慢,甚至比生成脏页的速度还慢,这样就造成了脏页累积,影响查询和更新性能。
MySQL 还有一个机制,可能让我们的查询变慢。当我们准备刷一个脏页时,如果这个数据页旁边的数据页刚好也是脏页,就会把这个相邻数据页也带着一起刷掉;而且这个相邻数据页的逻辑会蔓延,也就是对于每个相邻数据页,如果和他相邻的数据页也是脏页的话,也会被放到一起刷。
在 InnoDB 中,innodb_flush_neighbors
参数就是用来控制这个行为的,值为 1 表示会有“连坐”机制,值为 0 表示不找相邻脏页。
对于机械硬盘来说,找相邻脏页,可以减少很多随机 IO。机械硬盘的随机 IOPS 一般只有几百,这样会大幅提升系统性能。但是对于 SSD 这类 IOPS 比较高的设备来说,就不用找相邻脏页了,因为 IOPS 一般不是瓶颈,这种情况我们更需要减少 SQL 语句的响应时间。