undo log 回滚原理
前面介绍了 redo log,有了它便可以保证事务在提交之后数据不丢失。但是问题来了,如果事务执行到一半的时候需要回滚怎么办?比如一个事务里面有 4 个增删改语句,已经执行完两个了,Buffer Pool 的数据也被更新了,但是还有两个没有执行,而此时事务要回滚了,怎么办?
显然事务回滚的核心就在于,要将 Buffer Pool 里的数据变成事务执行前的状态,换句话说,就是把在 Buffer Pool 里执行的增删改操作给回滚了。
但问题是怎么回滚呢?由于 Buffer Pool 已经被修改了,所以在执行事务的时候,必须引入另外一种日志,也就是 undo log 日志。
undo log 日志记录的东西其实非常简单,用大白话解释就是:
- 比如要执行一个 insert 语句,那么在 undo log 日志里就记录一条对应的 delete 语句。当回滚的时候,把 insert 插入的行给删掉即可。
- 同理,如果要执行一个 delete 语句,那么在 undo log 日志里面就会把删除的行数据保存下来,并记录一条 insert 语句。当回滚的时候,再将删除的行插回去。
- 如果要执行一个 update 语句,那么在 undo log 日志里面就会把更新前的值保存下来,回滚的时候再 update 回去。
所以回滚没有想象的那么神秘,就是针对每一个增删改语句,在 undo log 日志中都会记录一个与之相反的语句。当事务回滚时,执行相反的语句,将事务执行时对 Buffer Pool 所做的修改给抵消掉。注意:这里不包含 SELECT 语句,因为它没有涉及修改,所以 undo log 中不需要记录任何东西。
所以执行事务时,不仅要写 redo log,还要写 undo log。前者保证事务提交之后数据不丢失,后者保证事务能够回滚。
undo log 长什么样子
那么 undo log 长什么样子呢?首先不同语句对应的 undo log 的类型不同,比如 insert 语句对应的类型就是 TRX_UNDO_INSERT_REC,它里面包含了以下内容:
- 该条日志的开始位置;
- 构成主键的每一列的长度和值;
- 表 ID;
- undo log 日志编号;
- undo log 日志类型;
- 该条日志的结束位置;
解释一下每一部分的含义,首先每个日志都要有自己的开始位置和结束位置,这没什么好说的。
然后插入数据的时候,必然要有一个主键(可以单个字段、也可以是多个字段组成的复合主键),即使创建表时没有显式指定主键,MySQL 也会弄一个隐藏的字段 row_id 作为主键。所以构成主键的每一列的长度和值指的就是插入数据的主键的每个列,它的长度是多少,具体的值是什么。
接下来是表 ID,因为数据是插入到表里的,所以要记录是在哪张表里插入的数据。
undo log 日志编号,因为在一个事务里会有多条 SQL 语句,所以会有多个 undo log 日志。而每个 undo log 日志都是有自己的编号的,在每个事务里的 undo log 日志的编号都是从 0 开始,然后依次递增。
undo log 日志也是有类型的,insert 语句的 undo log 类型就是 TRX_UNDO_INSERT_REC。
假设你插入了一条数据,现在要回滚,那么把该语句对应的 undo log 拿出来。通过 undo log 可以得知是在哪张表插入的数据,以及主键是什么,然后通过 delete 语句再将它删除掉即可。
以上是 insert 语句的 undo log,其它语句与之类似。
本文参考自:
- 儒猿技术窝《MySQL 实战高手》