前言
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
二者之间还是有很大的区别:
Server层
,而 redo log是InnoDB存储引擎
特有的。追加写
,而 redo log 写入方式是循环写
。关于bin log
的二进制日志格式,有以下三种类型:
STATEMENT
:增删改SQL语句,存储空间要求小ROW
:记录表行的更改情况,存储空间要求大MIXED
:混合场景,默认情况下STATEMENT,少数情况下ROWredo 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: