在Mysql系列–事务日志中,我们介绍了Mysql中的事务日志,这里主要介绍Mysql事务日志中的Redo log日志。
Redo log是什么?
Redo log是重做日志,提供前滚操作。Redo log是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成什么样的。Redo log用来恢复提交后的物理数据页,但只能恢复到最近一次提交的内容。
Redo log包括两部分:内存中的日志缓存(log buffer)和磁盘上的日志文件(log file),其中日志缓存是易失的,日志文件是持久化的。
在Innodb存储引擎中,Redo log是以块为单位进行存储的,称为Redo log block,每个块占512字节,因此log buffer(内存日志)、os buffer(文件系统缓存)以及redo log file on disk(日志文件)都是以512字节的块存储的。
Redo log block由三部分组成:日志块头(12)+日志数据(492)+日志块尾(8),当数据页的修改较多需要超过492字节的日志记录时,就会使用多个Redo log block来记录数据页的修改。
Redo log是以顺序的方式追加到文件尾,并且Redo log的文件大小(由innodb_log_file_size指定)是固定的,当文件满时必须要先刷盘才能继续写,这会影响Mysql的性能。为了解决这个问题,引入了Redo log group的概念,即group中存在多个Redo log文件,每次只使用其中一个Redo log文件,当写满了就切到另一个Redo log文件,并触发Redo log的刷盘。group中Redo log文件的个数由参数innodb_log_files_group控制,默认为2。
需要注意,group只是一个逻辑概念,并没有真实文件目录来表示该group,Redo log的存储位置是由参数innodb_log_group_home_dir指定的,默认在datadir下。
Redo log文件大小对性能影响较大,当文件大小较大时,通过Redo log恢复时间就较长,当文件大小较小时,会导致频繁切换Redo log。
Redo log持久化
在概念上,Innodb通过force log at commit机制实现事务的持久化,即在事务提交的时候,必须先将该事务的所有事务日志写入到redo log file和undo log file中进行持久化。在事务持久化的过程中,通过DoubleWrite技术来保证Redo log正确的持久化。
为了提升事务提交性能,Redo log一般都是先写入log buffer中,在后续合适时机将log buffer写入到log file中进行持久化,即刷盘。由于log buffer中的数据是存在内存中,是易失的,为了尽可能减少事务提交后数据丢失的影响,可以通过innodb_flush_log_at_trx_commit 控制redo log的刷盘,取值0、1、2:
- 0 : 提交事务的时候,不立即把 redo log buffer 里的数据刷入磁盘文件的,而是依靠 InnoDB 的主线程每秒执行一次刷新到磁盘。此时可能你提交事务了,结果 mysql 宕机了,然后此时内存里的数据全部丢失。
- 1 : 提交事务的时候,就必须把 redo log 从内存刷入到磁盘文件里去,只要事务提交成功,那么 redo log 就必然在磁盘里了。注意,因为操作系统的“延迟写”特性,此时的刷入只是写到了操作系统的缓冲区中,因此执行同步操作才能保证一定持久化到了硬盘中。
- 2 : 提交事务的时候,把 redo 日志写入磁盘文件对应的 os cache 缓存里去,而不是直接进入磁盘文件,可能 1 秒后才会把 os cache 里的数据写入到磁盘文件里去。
除此之外,还存在以下场景触发日志刷盘动作:
- 定时刷盘(默认1s一次),这个刷日志频率由innodb_flush_log_at_timeout控制。
- Log buffer中已使用内存超过一半
- 当有checkpoint时,checkpoint是指检查点,可以加快Mysql的重启恢复过程,在重启恢复过程中,只需要恢复checkpoint之后的日志。c heckpoint在一定程度上可以代表已刷盘的日志位置。
Redo log组提交
当innodb_flush_log_at_trx_commit=1时,在事务每次commit时,仍然需要有一次日志刷盘操作,这个操作将会成为事务并发的瓶颈。为了解决这个问题,Mysql提出了Redo log组提交,即将同时提交的事务一次刷盘,注意这里不是等待一段时间后批量刷盘,而是将同时提交的事务一次性刷盘。
实现Redo log组提交需要依赖LSN(Log Sequence Number),LSN是单调递增的,当多个事务同时提交时,按照以下流程执行:
- 获取log_mutex
- 若flushed_to_disk_lsn >= lsn,表示日志已经被刷盘,跳转5
- 若current_flush_lsn >= lsn,表示日志正在被刷盘,跳转5后进入等待状态
- 将小于lsn的日志刷盘(组提交)
- 释放log_mutex
在第4步中,在刷盘的过程中,顺便把较小的lsn对应的事务一起刷盘。
LSN不仅可用于Redo log组提交,还可以用于checkpoint。LSN占用8字节,包含以下信息:数据页的版本信息、写入的日志总量,通过LSN开始号码和结束号码计算和检查点的位置
LSN不仅存在于redo log中,还存在于数据页中,在每个数据页的头部,有一个fil_page_lsn记录了当前页最终的LSN值是多少。由于redo log和数据同时存在于内存和磁盘中,内存和磁盘间的数据存在一定的延迟,因此会有4个LSN。
可以通过show engine innodb status查看当前lsn相关信息。
在进行数据恢复过程中,通过数据页中的LSN值和redo log中的LSN值比较,如果数据页中的LSN值小于redo log中LSN值,则表示数据丢失了一部分,这时候可以通过redo log的记录来恢复到redo log中记录的LSN值时的状态。
数据刷盘时机-checkpoint
上文介绍了Redo log的刷盘,而数据也是以数据页的形式存在内存中,也是需要将其刷盘的。触发数据刷盘的规则只有一个:checkpoint,但触发checkpoint的场景有多个。当checkpoing触发后,将会把脏数据页和脏日志页都刷到磁盘中。
checkpoint目的有:
- 缩短数据库的恢复时间
- 数据页(buffer pool)不够用时,将数据脏页刷到磁盘
- redo log不可用时,将日志脏页刷到磁盘
checkpoint分为两类:
- sharp checkpoint:完全检查点,数据库正常关闭时,会触发将所有脏页(日志和数据)写回磁盘。在数据库正常运行过程中,不会触发sharp checkpoint。
- fuzzy checkpoint:模糊检查点,当满足一定的条件部分刷盘,一次只刷一部分,避免全局刷盘造成Mysql系统的卡顿。在数据库正常运行期间发生,存在4种情况触发该检查点。
触发fuzzy checkpoint的场景:
- master thread checkpoint:异步以每秒或10秒从缓冲池的脏页列表中刷一定比例页到磁盘中。可以通过innodb_io_capacity控制刷脏页数。
- flush_lru_list checkpoint: Mysql需要保证有足够可用的空闲页,当不足时从lru列表中移除足够的页,当这些页有脏页时,就会触发checkpoint。在5.6以后的版本,由page cleaner线程负责保证有足够的可用空闲页。通过innodb_lru_scan_depth可以控制可用页的数量。
- async/sync flush checkpoint:log file快满时,会批量触发数据页的回写,根据log file不可被覆盖的比例可分为同步刷盘和异步刷盘,当达到75%,则触发异步刷盘,当达到90%,则触发同步刷盘。在5.6以后的版本,由page cleaner线程负责保证有足够可用的log file。
- dirty page too much checkpoint:脏页太多检查点,为了保证buffer pool的空间可用性。脏页的比例由参数innodb_max_dirty_pages_pct控制。
更多精彩,请关注