不知道大家是否还记得事务的四大特性,现在我们回忆一下吧,持久性、一致性、隔离性、原子性。没错,我们今天讲的就是持久性。
一、啥是 redo log
我们想象有这么一个生产环境,就是如果我们只在内存的 Buffer Pool 中修改了页面,事务提交后突然发生了某个故障,导致内存中的数据都失效了,那么这个已经提交了的事务对数据库中所做的更改也就跟着丢失了,这你能忍?反正我是不能忍。🙂🙂🙂 这就是持久性遭到了破坏,那我们如何保证持久性呢? 有同学可能说每次事务提交之前把该事务所修改的所有页面都刷新到磁盘。但是,你有没有想过这样做有问题呢?
-
刷新一个完整的数据页太浪费了
有时候我们仅仅修改了某个页面中的一个字节,但是我们知道在 InnoDB 中是以页为单位来进行磁盘IO 的,也就是说我们在该事务提交时不得不将一个完整的页面从内存中刷新到磁盘,我们又知道一个页面默认是 16KB 大小,只修改一个字节就要刷新 16KB 的数据到磁盘上显然是太浪费了
-
随机 IO 比较慢
一个事务可能包含很多条 SQL 语句,而这些语句可能对 Buffer Pool 中不相邻的数据页进行操作。当把该事务修改过的数据页刷新到磁盘时会产生很多随机 IO,我们知道随机 IO 是比顺序 IO 慢的
我们还记得我们的目的是啥吗?不就是想让已经提交了的事务对数据库中数据所做的修改永久生效,即使后来系统崩溃,在重启后也能把这种修改恢复出来。所以我们其实没有必要在每次事务提交时就把该事务在内存中修改过的全部页面刷新到磁盘,只需要把修改了哪些东西记录一下就好。那用什么东东来记录呢,此时我们的 redo log 就登场了。
redo log 顾名思义,就是重做日志,我们只需要将对数据页做了哪些修改记录到 redo log 就行了,当系统崩溃了,重启之后只要按照 redo log 所记录的步骤重新更新一下数据页,那么该事务对数据库中所做的修改又可以被恢复出来,满足持久性。那为啥用 redo log 呢?
- redo 日志占用的空间非常小
存储表空间ID、页号、偏移量以及需要更新的值所需的存储空间是很小的 - redo 日志是顺序写入磁盘的
在执行事务的过程中,每执行一条语句,就可能产生若干条 redo 日志,这些日志是按照产生的顺序写入磁盘的,也就是使用顺序IO,速度比随机 IO 更快
讲了啥是 redo log,我们现在看看真实的 redo log 到底长啥样吧
二、redo log 格式
redo log 的格式:
![](https://gitee.com/kuangty/blogImage/raw/master/img/2021-11-01_18-41-14.png)
- type :该条 redo 日志的类型。
- space ID :表空间 ID。
- page number :页号。
- data :该条 redo 日志的具体内容。
这里 redo log 日志的类型比较多,我在这里举两个例子吧:
![](https://gitee.com/kuangty/blogImage/raw/master/img/2021-11-01_19-18-00.png)
![](https://gitee.com/kuangty/blogImage/raw/master/img/2021-11-01_19-18-09.png)
还有比较复杂的 redo log 的类型,我这里就不做详述了,感兴趣的同学可以自行去学习 🤣🤣🤣
三、Mini-Transaction 的概念
MySQL 把对底层页面中的一次原子访问的过程称之为一个 Mini-Transaction ,简称 mtr ,比如向某个索引对应的 B+ 树中插入一条记录的过程就是一个 Mini-Transaction 。通过上边的叙述我们也知道,一个所谓的 mtr 可以包含一组 redo 日志,在进行奔溃恢复时这一组 redo 日志作为一个不可分割的整体
一个事务可以包含若干条语句,每一条语句其实是由若干个 mtr 组成,每一个 mtr 又可以包含若干条 redo 日志,画个图表示它们的关系就是这样:
![](https://gitee.com/kuangty/blogImage/raw/master/img/2021-11-01_23-36-39.png)
四、redo日志的写入过程
1、redo日志的写入过程
redo 日志也是需要存储的啊,所以 MySQL 把它放在了大小为 512 字节的页中,但是我们把用来存储 redo 日志的页称为 block,画一个图来说明更加形象一点:
![](https://gitee.com/kuangty/blogImage/raw/master/img/2021-11-01_19-28-45.png)
真正的 redo 日志都是存储到占用 496 字节大小的 log block body 中,图中的 log block header 和 log block trailer 存储的是一些管理信息,那这些管理信息是啥呢,我们画一个图说明:
![](https://gitee.com/kuangty/blogImage/raw/master/img/2021-11-01_19-38-29.png)
LOG_BLOCK_HDR_NO
:每一个 block 都有一个大于 0 的唯一标号,本属性就表示该标号LOG_BLOCK_HDR_DATA_LEN
:表示 block 中已经使用了多少字节,初始值为 12 (因为 log block body 从第12个字节处开始)。随着往 block 中写入的 redo 日志越来也多,本属性值也跟着增长。如果 log block body 已经被全部写满,那么本属性的值被设置为 512LOG_BLOCK_FIRST_REC_GROUP
:一条 redo 日志也可以称之为一条 redo 日志记录( redo log record ),一个 mtr 会生产多条 redo 日志记录,这些 redo 日志记录被称之为一个 redo 日志记录组( redo logrecord group )。LOG_BLOCK_FIRST_REC_GROUP
就代表该 block 中第一个 mtr 生成的 redo 日志记录组的偏移量(其实也就是这个 block 里第一个 mtr 生成的第一条 redo 日志的偏移量)LOG_BLOCK_CHECKPOINT_NO
:表示所谓的 checkpoint 的序号LOG_BLOCK_CHECKSUM
:表示 block 的校验值,用于正确性校验
2、redo日志缓冲区
为了解决磁盘速度过慢的问题而引入了 Buffer Pool 。同理,写入 redo 日志时也不能直接直接写到磁盘上,实际上在服务器启动时就向操作系统申请了一大片称之为 redo log buffer 的连续内存空间,也叫 redo日志缓冲区 ,简称为 log buffer 。这片内存空间被划分成若干个连续的