【MySQL】六、MySQL的Redo日志和Undo日志介绍

【MySQL】六、MySQL的Redo日志和Undo日志介绍

本文主要介绍MySQL数据库的Redo日志和Undo日志

5 redo 日志

5.1 重新回顾redo日志对于事务提交后,数据绝对不会丢失的意义

在我们执行增删改操作的时候,首先会在Buffer Pool中更新缓存页,在更新完Buffer Pool中的缓存页之后,必须要写一条redo log,这样才能记录下来我们对数据库做的修改。

redo log可以保证我们事务提交之后,如果事务中的增删改SQL语句更新的缓存页还没刷到磁盘上去,此时MySQL宕机了,那么MySQL重启过后,就可以把redo log重做一遍,恢复出来事务当时更新的缓存页,然后再把缓存页刷到磁盘就可以了。

redo log本质是保证事务提交之后,修改的数据绝对不会丢失的。

我们都知道,执行增删改SQL语句的时候,都是针对一个表中的某些数据去执行的,此时的话,首先必须找到这个表对应的表空间,然后找到表空间对应的磁盘文件,接着从磁盘文件里把你要更新的那批数据所在的数据页从磁盘读取出来,放到Buffer Pool的缓存页里去,如下图所示:

image-20211007204339220

接着实际上你的增删改SQL语句就会针对Buffer Pool中的缓存页去执行你的更新逻辑,比如插入一行数据,或者更新一行数据,或者是删除一行数据。

其实更新缓存页的时候,会更新free链表、flush链表、lru链表,然后有专门的后台IO线程,不定时的根据flush链表、lru链表,会把你更新过的缓存页刷新回磁盘文件的数据页里去,如下图所示:

image-20211007205037639

这个机制最大的漏洞就在于万一你一个事务里有增删改SQL更新了缓存页,然后事务提交了,结果万一你还没来得及让IO线程把缓存页刷新到磁盘文件里,此时MySQL宕机了,然后内存数据丢失了,你事务更新的数据就丢失了。

但是也不可能每次事务一提交,就把事务更新的缓存页都刷新回磁盘文件里去,因为缓存页刷新到磁盘文件,是随机磁盘读写,性能是相当的差,这会导致数据库性能和并发能力都很弱。

所以才会引入redo log机制,这个机制就是说,你事务提交的时候,绝对是保证把你对缓存页做的修改以日志的形式,写入到redo log日志文件里去的。

这种日志大致的格式如下,对表空间xx中的数据页xx中的偏移量为xxxx的地方更新了数据xxxx,如下图所示:

image-20211007205658189

只要你事务提交的时候保证你做的修改以日志形式写入redo log日志,那么哪怕此时机器突然宕机了,也没关系。

因为MySQL重启之后,把之前事务更新过做的修改根据redo log在Buffer Pool里重做一遍就可以了,就可以恢复出来当时事务对缓存页做的修改,然后找时机再把缓存页刷入磁盘文件里去。

事务提交的时候把修改过的缓存页都刷入磁盘,跟事务提交的时候把做的修改的redo log都写入日志文件,不是都写磁盘么?差别在哪里?

实际上,如果把修改过的缓存页都刷入磁盘,缓存页一个就是16kb,数据比较大,刷入磁盘比较耗时,而且可能就修改了缓存页里的几个字节的数据,难道就把完整的缓存页刷入磁盘吗?而且缓存页刷入磁盘是随机写磁盘,性能是很差的,因为一个缓存页对应的位置可能在磁盘文件的一个随机位置,比如偏移量为45336这个地方。

但是如果是写redo log,一行redo log可能就占据几十个字节,就包含表空间号、数据页号、磁盘文件偏移量、更新值,这个写入磁盘速度很快。

此外,redo log写日志,是顺序写入磁盘文件,每次都是追加到磁盘文件末尾去,速度也是很快的。所以提交事务的时候,用redo log的形式记录下来你做的修改,性能会远远超过刷缓存页的方式,这也可以让数据库的并发能力更强。

5.2 在Buffer Pool执行完增删改之后,写入日志文件的redo log长什么样?

redo log里本质上记录的就是在对某个表空间的某个数据页的某个偏移量的地方修改了几个字节的值,具体修改的值是什么,他里面需要记录的就是表空间号+数据页号+偏移量+修改几个字节的值+具体的值。

所以根据你修改了数据页里的几个字节的值,redo log就划分为了不同的类型,MLOG_1BYTE类型的日志指的就是修改了1个字节的值,MLOG_2BYTE类型的日志指的就是修改了2个字节的值,以此类推,还有修改了4个字节的值得日志类型,修改了8个字节的值的日志类型。

当然如果要是一下子修改了一大串的值,类型就是MLOG_WRITE_STRING,就是代表你一下子在哪个数据页的某个偏移量的位置插入或者修改了一大串的值。

所以其实一条redo log看起来大致的结构如下所示:

日志类型(就是类似MLOG_1BYTE之类的),表空间ID,数据页号,数据页中的偏移量,具体修改的数据

大致就是一条redo log中依次排列上述的一些东西,这条redo log表达的语义就很明确了,他的类型是什么,类型就告诉了你他这次增删改操作修改了多少字节的数据;

然后再哪个表空间里操作的,这个就是跟你SQL再哪个表里执行的是对应的;接着就是在这个表空间的哪个数据页里执行的,在数据页的哪个偏移量开始执行的,具体更新的数据是哪些。

有了上述信息,就可以精准完美的还原出来一次数据增删改操作做的变动了。

只不过如果是MLOG_WRITE_STRING类型的日志,因为不知道具体修改了多少字节的数据,所以其实会多一个修改数据长度,就告诉你他这次修改了多少字节的数据,如下所示他的格式:

日志类型(就是类似MLOG_1BYTE之类的),表空间ID,数据页号,数据页中的偏移量,修改数据长度,具体修改的数据

5.3 redo log是直接一条一条写入文件吗?

其实MySQL内有另外一个数据结构,叫做redo log block,大概可以理解为平时我们的数据不是存放在数据页了的吗,用一页一页的数据页来存放数据,那么对于redo log页不是单行单行的写入日志文件的,他是用一个redo log block来存放多个单行日志的。

一个redo log block是512字节,这个redo log block的512字节分为3个部分,一个是12字节的header块头,一个是496字节的body块体,一个是4字节的trailer块尾

image-20211007212954412

在这里面,12字节的header头又分为了4个部分:

  1. 包括4个字节的block no,就是块唯一编号;
  2. 2个字节的data length,就是block里写入了多少字节数据;
  3. 2个字节的first record group,这个是说每个事务都会有多个redo log,是一个redo log group,即一组redo log,那么在这个block里的第一组redo log的偏移量就是这两个字节存储的。
  4. 4个字节的checkpoint on

所以其实对于redo log而言,确实是不停的追加写入到redo log磁盘文件里去的,但是其实每一个redo log都是写入到文件里的一个redo log block里去的,一个block最多放496字节的redo log日志。

那么到底一个一个的redo log block在日志文件里是怎么存在的?一条一条的redo log又是如何写入日志文件里的redo log block里去的呢?

假设你有一个redo log日志文件,平时我们往里面写数据,大致可以认为是从第一行开始的,从左往右写,可能会有很多行。既然要写第一个redo log了,是不是应该起码是先在内存里把这个redo log给弄到一个redo log block数据结构里去?

然后应该等内存里的一个redo log block的512字节都满了,再一次性把这个redo logblock写入磁盘文件?

其实按照我们说的,一个redo log block就是512字节,那么是不是真正写入的时候,把这个redo log block的512字节的数据,就写入到redo log文件里去就可以了?那么redo log文件中就多了一个block.

6 undo log

6.1 undo log 日志

我们在一个事务里执行一些增删改的操作,必然是先把对应的数据页从磁盘加载出来放buffer pool的缓存页里,然后再缓存页里执行增删改操作,同时记录redo log。那万一事务里的增删改执行到了一半,结果就回滚事务了呢?此时就需要另外一种日志,undo log回滚日志。

这个回滚日志,他记录的东西其实很简单,比如要是在缓存页里执行了一个insert语句,那么此时在undo log日志里,对这个操作激励的回滚日志就必须是有一个主键和一个对应的delete操作,要能让你把这次insert操作给回退了。

比如要执行的是delete语句,那么起码要把你删除的那条数据记录下来,如果要回滚,应该执行一个insert操作把那条数据插入回去。

如果要执行的是update语句,要把更新之前的那个值记录下俩,回滚的时候重新update一下。

6.2 看看insert语句的undo log回滚日志长什么样?

INSERT语句的undo log的类型是TRX_UNDO_INSERT_REC,这个undo log里面包含了以下一些东西:

  • 这条日志的开始位置

  • 主键的各列长度和值

  • 表id

  • undo log日志编号

  • undo log日志类型

  • 这条日志结束位置

首先,一条日志必须得有自己的一个开始位置,那么主键的各列长度和值是什么意思?你插入一条数据,必然会有一个主键,如果你自己指定了一个主键,那么可能这个主键就是一个列,比如id之类的,也可能是多个列组成的一个主键,比如”id+name+type”三个字段组成的一个联合主键。

所以这个主键的各列长度和值,意思就是你插入的这条数据的主键的每个列,他的长度是多少,具体的值是多少,即使你没有设置主键,MySQL自己也会给你弄一个row_id作为隐藏字段,做你的主键。

接着就是表id,你插入一条数据必然是往一个表里插入数据的,当然得有一个表id,记录下来是在哪个表里插入的数据了。

undo log日志编号就是每个undo log日志都有自己的编号的。

而在一个事务里会有多个sql语句,就会有多个undo log日志,在每个事务里的undo log日志的编号都是从0开始的,然后依次递增。

至于undo log日志类型,就是TRX_UNDO_INSERT_REC,insert 语句的undo log日志类型就是这个东西。

最后一个undo log日志的结束位置就是告诉你undo log日志结束的位置是什么。

【MySQL】六、MySQL的Redo日志和Undo日志介绍

https://www.shuiwh.com/posts/learn-mysql-006/

作者

水无痕

发布于

2025-07-17

更新于

2025-07-19

许可协议