【MySQL】深入解析日志系统:undo log、redo log、bin log
前言
MySQL数据库提供了功能强大的日志系统,其中比较重要的是:undolog、redolog、binlog,今天来深入学习下这三个日志实现细节。
1、undo log
1.1、undo log 是什么
undolog一般叫回滚日志,事务回滚rollback功能就是通过undolog实现的,通过undolog保证了为事务的原子性,undolog主要功能如下:
- 事务回滚
- MVCC
1.2、事务回滚
当开启一段事务还未提交时,事务中的操作可能会出现错误异常,这时候就可以通过undo log将事务中的操作进行回滚(rollback),意思是回到事务开启前那个状态。例如:开启事务后我对表中某条记录进行修改(将该记录字段值由a ——> b ——> c),如果从整个修改过程中出现异常,事务就会回滚,字段的值就回到最初的起点(值为a)。
事务如何通过undo log进行回滚操作呢?这个很好理解,我们只需要在undo log日志中记录事务中的反向操作即可,发生回滚时直接通过undolog中记录的反向操作进行恢复,例如:
事务进行
insert操作,undo log记录delete操作 事务进行delete操作,undo log记录insert操作 事务进行update操作(a改为b),undolog记录update操作(b改为a)
接下来了解一下事务是如何通过undo log完成回滚的(undo log版本链),对于InnoDB存储引擎而言,数据页中的每行数据都会分配两个字段:trx_id和roll_pointer,在了解这个之后就通过一张图直观的表达undo log记录了:
上图中,trx_id代表事务id,记录了这一系列事务操作是基于哪个事务;roll_pointer代表回滚指针,就是当要发生rollback回滚操作时,就通过roll_pointer进行回滚,这个链表称为版本链。
buffer pool 中有 undo 页,不仅对数据页修改操作会记录到redo log buffer,对 undo 页修改操作也会记录到 redo log buffer,这样就通过redo log保证了事务持久性。
当事务Commit之后,undo 页本身就没有利用价值了,此时通过后台线程中的Master Thread或Purge Thread进行 undo 页 的回收工作。

2、redo log
2.1、redo log 是什么
redo log又称重做日志,保证了事务的持久性,当我们对缓冲池中的数据页进行了修改(修改后变成脏页),但是脏页数据是存在于Buffer Pool缓冲池,缓冲池占用的就是操作系统内存空间,所以数据页本质也是存在内存中的,内存有个特点就是断电即失。
所以当脏页数据还没有刷入磁盘,此时数据库服务发生宕机,那么脏页数据就会因为宕机而丢失,如何恢复这些没刷盘得脏页数据呢?这时候redo log就派上了用场,具体流程可参考下图:

redo log通过WAL(Write-Ahead Logging)来进行故障恢复(crash-safe),所谓WAL大白话先写日志,后写磁盘。当我们对缓存页进行了修改后(变成脏页),我们就将本次操作写入到redo log buffer中,当事务Commit时就先将redo log buffer中记录通过后台线程刷到磁盘中(事务提交是redo log默认刷盘时机),此时脏页还没有刷入磁盘,但只要redo log成功刷盘就可以认为本次的修改操作完成了,因为就算发生了故障导致脏页数据丢失也可以通过磁盘redo log恢复,需要注意redo log记录的是物理操作,例如:对AAA数据页BBB偏移量位置做了CCC更新,这跟undo log区别还是挺大的:
事务提交
前崩溃,通过undo log回滚事务 事务提交后崩溃,通过redo log恢复事务
2.2、redo log 刷盘
上面已经介绍过了,redo log记录先写入到redo log buffer中,然后通过后台线程进行刷盘,也就是说最后还是从 redo log buffer 同步到硬盘中,那么redo log buffer何时进行刷盘操作呢?主要是以下几种情况,默认情况下,redo log在事务提交时就会进行一次redo log刷盘:
- Master Thread每秒刷盘一次
- redo log buffer 剩余空间 < 1/2
- 通过innodb_flush_log_at_trx_commit参数控制
- 0:有事务提交的情况下,每秒刷盘一次
- 1:每次提交事务,刷盘一次(
默认,性能差)- 2:每次提交事务,把日志记录放到OS内核缓冲区,刷盘时机交给OS(性能好)
这有个疑问点,为啥事务提交时不直接将脏页刷盘呢,何苦还要将 redo log buffer 中记录进行刷盘,然后脏页再刷盘呢,这不多了一步流程吗?之所以多了 redo log 刷盘这步操作,主要原因:
redo log刷盘操作采用磁盘顺序写方式进行的缓存页刷盘操作采用随机写方式顺序写比随机写性能更优秀
2.3、redo log 硬盘文件
上面提到了redo log的刷盘操作采用顺序写方式进行,接下来咱们看下 redo log 文件在硬盘中是怎样的方式存在的。
redo log文件好像一个甜甜圈🍩,它是以ib_logfile文件组形式存在(也就是多个ib_logfile构成一个重做日志组),每个重做日志组中最少2个ib_logfile,每个ib_logfile占用内存1GB,当ib_logfile-1写满之后就开始写入ib_logfile2,以此类推:图片来自于《MySQL实战45讲》:

上图中的重做日志组包含了4个重做日志文件ib_logfile,按照圆环顺时针顺序写入文件,这种写入磁盘的方式也叫循环写:
write pos:当前redo log文件写到了哪个位置 check point:目前redo log文件哪些记录可以被覆盖
两个指针中间绿色部分表示还剩余多少可写入空间,也就是redo log 文件的可用空间了,当数据页进行刷盘操作(CheckPoint)时,check point指针也会顺时针进行覆盖(黄色变成绿色);当write pos追上了check point就说明 redo log 文件存满了,那就要强制CheckPoint了,将缓冲池中的脏页刷盘,然后再移动check point指针,这样就可以继续向重做日志组中写入数据了。
说到这里大家就该明白,当数据页刷盘后,check point也会顺时针移动,将无用的redo log记录覆盖掉,所以redo log大小需要好好斟酌,如果设置太大,那么故障恢复crash-safe时间会很久;如果设置太小,就会频繁发生刷盘导致性能抖动!
3、bin log
3.1、bin log 是什么
bin log常称作二进制日志,该日志主要有两个功能:
- 备份恢复
- 主从复制
每次事务进行提交时,都会将增、删、改操作以追加的方式记录到bin log文件中。这样也就理解了为什么该日志具备备份恢复和主从复制的功能,关于备份恢复比较好理解,根据bin log日志中记录的二进制操作记录恢复即可;主从复制常用于MySQL主从集群搭建,MySQL从节点通过监听主节点bin log日志进行同步即可。
3.2、bin log 和 redo log 区别
bin log 和 redo log二者之间还是有很大的区别:
- bin log 属于MySQL体系架构的
Server层,而 redo log是InnoDB存储引擎特有的。 - bin log 写入方式是
追加写,而 redo log 写入方式是循环写。 - bin log 中记录的是二进制日志(3种类型),而 redo log 中记录的是物理日志信息。
- bin log 用于备份恢复、主从复制,而 redo log 用于故障恢复。
关于bin log的二进制日志格式,有以下三种类型:
STATEMENT:增删改SQL语句,存储空间要求小ROW:记录表行的更改情况,存储空间要求大MIXED:混合场景,默认情况下STATEMENT,少数情况下ROW
redo log 不具备数据备份功能的原因是由于 redo log 文件组采用的是循环写方式,所以当脏页刷盘时redo log文件组内容会进行覆盖;之所以bin log不具备crash-safe原因是我们很难判断到底从那个位置进行恢复,而redo log有write pos和check point指针指明了哪些数据需要恢复。
3.3、bin log 刷盘
bin log属于MySQL体系架构的Server层,事务操作进行过程中,会把日志信息先记录到bin log cache中,等到事务提交后会将bin log cache中记录刷盘到bin log 文件中。
这里需要注意,无论一个事务中包含了多少个增删改操作,都要一次性写入,不可拆分。当一个线程执行事务操作时,MySQL就会为该线程分配bin log cache,而且一个线程某一时刻最多只能执行一个事务。
bin log 刷刷盘时机通过参数sync_binlog控制:
0:每次提交事务写到内核缓冲区,不刷盘(由OS决定何时刷盘) 1:每次提交事务写到内核缓冲区,马上刷盘 N:每次提交事务写到内核缓冲区,累积 N 个事务后才刷盘(N > 1)
3.4、两阶段提交
当事务进行Commit操作时,redo log 和 bin log都会被刷盘持久化保存,但是可能会出现以下两种情况:
bin log刷盘后,redo log还未来得及刷盘,数据库宕机,数据不一致。 redo log刷盘后,bin log还未来得及刷盘,数据库宕机,数据不一致。
说到这里大概知道两阶段提交其实就是为了防止这两个日志不一致,它将事务Commit操作分为两个阶段:Prepare、Commit:
- Prepare:
XID(内部 XA 事务的 ID)写入到redo log,同时将redo log对应的事务状态设置为prepare,然后将redo log持久化到磁盘(默认redo log刷盘策略); - Commit:
XID写入到bin log,马上将bin log刷盘(sync_binlog = 1),接着调用引擎的提交事务接口,将redo log状态设置为commit,只要bin log写磁盘成功,就算redo log的状态还是prepare也没有关系,一样会被认为事务已经执行成功。
图片来自于小林coding:

转载自:https://juejin.cn/post/7346111233605402678