Mysql是如何刷脏页

mysql的大部分操作都是在内存中执行,以一个update为例,如果要更新的数据所在page已经被加载到内存中了,那么整个update过程其实就是更改内存该页中的值,外加写redo log。

当一个sql突然变慢,可能由于myql正在刷脏页,如果一次操作刷的脏页很多,那么是会影响性能的。比如select一个很大的结果集,那么mysql可能要在buffer pool中申请好几个page,那么如果根据LRU淘汰的页是脏页,那么必须先刷脏变成干净页,然后才能复用。

那么什么情况下会,mysql会进行刷脏呢?

内存不够用

如果内存不够用了,这其实是常态,一个长时间使用的mysql,buffer pool中很少有干净页,每次操作都需要从磁盘加载数据进buffer pool的话,都会根据LRU算法淘汰旧数据,如上所说,如果淘汰的是脏页必须先刷脏。而查询结果集很大是会导致查询变慢的。

redo log写满了

redo log在mysql中,是非常重要的存在,WAL,奔溃恢复等都离不开redo log,所以当redo log写满了,mysql必须停止所有的更新操作,然后把checkpoint往前推进,那么推进的这一部分对应的脏页都需要刷到磁盘上。

mysql正常关闭

mysql正常关闭时后,会将内存中的脏页全部刷到磁盘上。

mysql空闲时

mysql在服务器负载较小时会主动刷脏页。

以上四种,后面两种其实不需要去关心,而可能会有性能影响的是前两种,因为mysql可能会一下刷很多脏页,那么怎么去优化呢?

刷脏页主要就是磁盘的写操作,那么mysql首先要知道,磁盘的写能力。

mysql有两个参数来设置磁盘的io能力,分别是innodb_io_capacity和innodb_io_capacity_max,分别表示磁盘io能力和极限。前者可以设置成磁盘的IOPS,即每秒io次数(可以用fio命令测试,可以多测几次)。

想一下如果你用的是ssd那么这个值可以设置到10000到20000,而你却设置成200,那么mysql在刷脏页时肯定是刷的“小心翼翼”。

当然,mysql并不会每次刷脏页时都按照这个读写能力去控制刷脏页的速度,他会根据当前mysql的脏页比例以及当前redolog的LSN和checkpoint的LSN的差值来计算出一个刷脏速率。

脏页比例是通过Innodb_buffer_pool_pages_dirty和Innodb_buffer_pool_pages_total计算

show status like "%pages_dirty%";
show status like "%pages_total%";

 总结

以一个问题开始,mysql怎么判断某页是否是脏页?

说下个人理解,系统会维护一个LSN,每次在记录日志时该值都会增加,每个数据页的头部都会记录这个值,checkpoint也会对应一个LSN,这个LSN是内存中最早的脏页的LSN,以一个update举例,过程如下:

记录日志到内存中的redo log buffer,内存中该页的LSN增加,为旧LSN + 日志大小

修改内存中的数据页,该页的LSN增加同样的值

根据innodb_flush_log_at_trx_commit = 1(通常会这么设置),将redo log buffer刷到redo log file,文件中保存该日志的page对应的LSN增加同样的值。

而mysql刷脏时,idb文件中保存该数据的page也会增加

回到问题,如何判断一个页是否是脏页?只要判断该页的LSN如果大于checkpoint的LSN,那么该页就是脏页,同样的,mysql在崩溃恢复时,会从redo log的checkpoint处开始进行重放操作,那么redo log对应磁盘上的页大于LSN时,表示需要重放,否则跳过该log。

关于checkpoint和lsn的一些杂记

每个数据页都有LSN
redo log有两个LSN,一个是in buffer,一个是on disk,show innodb engine status可以查看
数据库重启时,检查redo log file,对比log对应的page的LSN和redo log file的LSN值,如果小于

更新时先写内存page(page有一个LSN),然后写log进log buffer(LSN),commit时innodb_flush_log_at_trx_commit写日志进磁盘file(磁盘pageLSN)

在 Innodb 每次都取最老的 modified page 对应的 LSN,并将此脏页的 LSN 作为 Checkpoint 点记录到日志文件,意思就是 “此 LSN 之前对应的日志和数据都已经刷新到磁盘” 。

当 MySQL 启动做崩溃恢复时,会从 last checkpoint 对应的 LSN 开始扫描 redo log ,并将其应用到 buffer pool,直到 last checkpoint 对应的 LSN 等于 log flushed up to 对应的 LSN,则恢复完成。
(加入有两个脏页产生了
LSN = 第二个脏页的LSN
checkpointLSN = 第一个脏页的LSN,重启时从checkpoint(last checkpoint at LSN)开始遍历日志,第一条日志对应磁盘page的LSN < redo log LSN,开始重放至buffer pool,递增last checkpoint at LSN,第二条日志对应磁盘的pageLSN <= redo log LSN, 递增last checkpoint at LSN直到last checkpoint at LSN = redo log LSN = log flush up to = lsn)

当数据库宕机时,数据库不需要重做所有日志,因为CheckPoint之前的页都已经刷新回磁盘。只需对CheckPoint后的重做日志进行恢复,从而缩短恢复时间。

 

参考: 《mysql实战45讲》

Leave a comment

电子邮件地址不会被公开。 必填项已用*标注