第一次了解mysql的时候,看到了undo log这个名词,却不知道undo log是干什么,为了能够继续看明白一些mysql的资料,不得不先弄明白undo log是什么? undo log的原理是什么?它与数据库的其它特性如何配何。
这篇笔记只从原理上分析,不涉及具体的实现方法。

undo log是什么?

undo log是一种日志,日志中记录对于数据库的反向操作。
如果把数据库的内容当做一种状态机,那么数据的写操作就是修改状态机的命令,而undo 就对应修改状态机的反向命令。
所以理论上每一个对于状态机修改的命令都会产生对应一条相当的undo log,以便事务回滚的时候,能够把状态机修改到事务原来的样子。
假如我们有一个事务:
create table table1(c1 int);
begin transaction;
insert into table1 (100); //数据库执行这一条命令的时候应该产生一条undo日志,能够把这语句对于状态机的修改回复到原来没有修改的状态 , undo 应该是 delete table1 where c1 = 100;
insert into table1(200); // undo 应该是 delete table1 where c1 = 200;
update table1 set c1 = 300 where c1 = 200; // undo: update table1 set c1 = 200 where c1 = 300
rollback;

为什么要有undo log, 或者说undo log解决了什么问题?

其实这个问题问的不好,因为undo是设计出来,没有什么直接的因素说非得有undo log, 自己比较熟习的postgres就没有undo log。下面就与postgres进行比较,来看mysql为什么有undo
mysql与postgres都是基于mvcc的,但是mysql与postgres对于mvcc的实现不太一样。
为什么要有mvcc? 因为mvcc与它的前辈lock based相比,能够实现读写不冲突这一个特性,如果没有mvcc,一个事务读一条数据,另外一个事务写同一条数据,这两个事务是无法并发执行的,后一个事务必须要等前一个事务执行完成之后才能执行,但是有了mvcc,这两个事务就可以并发执行,这及大的提高的数据库的性能,以至于现在主流的数据库都实现了mvcc。
但是各个数据对于mvcc的实现略有不同:
其中mysql的实现就有undo log,而postgres的实现里面没有undo log.但是postgres里面有clog, clog记录了每一事务的状态。
postgres的每一行的所有的版本是存放一起的,它允许保留aborted事务产生的版本, mysql的最新版本保存在表空间,而历史版本则保存在undo log里面。mysql在事务回滚的时候,应该是同步利用undo log把最新版本的恢复成修改之前的状态,同时删除对应事务产生的undo log,而postgres则是通过异步的vaccum,来把aborted事务产生的历史版本给删除。
mvcc里面最重要的一点就是行可见性判断。这里简单的描述一下postgres的行可见性判断与mysql的行可见性判断。

  • mysql与postgres都会为每一个事务赋予一个xid,这个xid在数据库内部是单高递增的,它唯一标识了一个事务,同时它定义了数据库中事务相关事件的先后顺序。(这里的事务相关事件主要指begin transaction)
  • mvcc里面的snapshot读要求每一个事务(或者第一个语句,因为事务隔离级别的不同决定是一个事务一个snapshot还是一个语句一个snapshot)都要有一个snapshot,而mysql与postgres都选择使用当前活动事务的列表来作为snapshot. 每一个事务开始的时候(一个语句开始的时候)获取一下当前整个系统所有已经开始但没有结束的事务的xid.
  • mysql与postgres对于每一行的每一个版本都记录了两个字段x_min, x_max(mysql不知道叫啥,但是意思应该差不多), x_xmin代表创建这一个版本对应事务的xid, x_max表示删除这个版本对应的事务的xid
  • 判断一个版本是否可见,postgres需要以下几个信息:
  • 当前事务的snapshot(活动事务列表)
  • clog,可以根据xid来查询当前事务的状态,事务有三个状态pending, committed, aborted
  • 一个版本上的x_min, x_max
  • 首先,postgres先看一下,x_min对应的xid是否在snapshot中能够找到,如果能够找到,说明对应snapshot开始的时候,创建这个版本的事务还没有结束,直接返回该版本不可见
  • 如果x_min在在snapshot里面找不到,说明在取snapshot的时候,创建这一行的事务已经结束,事务结束分两种状态,aborted, committed
  • 通过查询clog,看x_min对应的事务是不是aborted, 如果是aborted, 说明创建这一行的事务已经回滚, 该行不可见
  • 如果事务是committed: 说明创建该行的动作在snapshot之前已经结束,这个时候还需要分情况:
  • 该行的x_max为空,就行是最新的版本,直接返回该行可见
  • 该行有x_max, 但是x_max对应的事务也在snapshot列表里面,说明取snapshot的时候,删除该行的事务还没有结束,该操作对应snapshot不可见,返回该行可见。
  • 该行有x_max, 同时x_max也不在snapshot列表里面,但是x_max对应的clog为aborted, 说明删除操作对应的事务aborted,返回该行可见
  • 该行有x_max, 同时x_max也不在snapshot列表里面,但是x_max对应的clog为committed, 说明删除该行的操作在snapshot之前已经成功,返回该行不可见。
  • mysql的行可见性应该会简单一点,因为历史版本里面只保存成功提交的版本
  • 行可见性判断的几个参数:对应事务(语句)的snapshot, x_min, x_max
  • x_min存在于snapshot中,该行不可见
  • x_min不存在于snapshot中,没有x_max, 该行可见。(该行是最新版本ujn8)
  • x_min不存在于snapshot中,同时x_max也不存在于snapshot列表中,该行不可见,说明删除操作在snapshot之前已经完成。

什么时候要使用到undo

上面已经提到,undo这个动作发生成rollback一个事务的时候。
同时也发生在数据recovery的时候,因为有些事务正在执行过程中,数据crash了,那么数据库重启做完redo后,要把对应没有提交的事务的动作undo一下。
同时undo log记录历史版本,读历史版本的时候,也要从undo log里面去读。

以上关于mysql的都是自己从互联网上看资料得来的,理解的并不一定正确,后面会根据mysql代码来验证一下。