1. 事务的四大特性
事务有4种特性:原子性、一致性、隔离性和持久性。那么事务的四种特性到底是基于什么机制实现呢?
事务的隔离性由锁机制实现。
事务的原子性、一致性和持久性由事务的 redo 日志和 undo 日志来保证。
redo log 称为重做日志 ,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性。
undo log 称为回滚日志 ,回滚行记录到某个特定版本,用来保证事务的原子性、一致性。
redo log 是存储引擎层生成的日志,记录的是物理级别上的页修改操作,比如页号xxx,偏移量yyy,写入了zzz数据,主要是为了保证数据的可靠性。
undo log 是存储引擎层生成的日志,记录的是逻辑操作的日志,比如对某一行数据进行了 insert 语句操作,那么undo log 就记录一条与之相反的 delete 操作。主要用于事务的回滚和一致性非锁定读(mvcc)。
2. 如何理解undo log
事务需要保证原子性,也就是事务中的操作要么全部完成,要么什么都不做。但有时候事务执行到一半会出现一些情况:
- 事务执行过程中可能遇到各种错误,比如服务器本身的错误,操作系统错误,甚至是突然断电导致的错误。
- 程序员可以再事务执行过程中手动输入 Rollback 语句结束当前事务的执行。
这两种情况都会导致事务执行到一半就结束,但是在事务执行过程中可能已经修改了很多东西。为了保证事务的原子性,我们需要改回原来的样子,这个过程称为回滚。这就造成了一个假象:这个事务看起来什么都没做,所以符合原子性要求。你插入了一条记录,回滚对应的操作就是把这条记录删除掉;你更新了一条记录,回滚对应的操作就是那该记录更新回旧值;你删除了一条记录,回滚对应的操作自然就是把该记录再插进去。
因此,每当我们要对一条记录进行改动时(这里的改动是指 insert,delete,update),都需要留一手,把回滚时所需要的东西都记下来。比如:
- 你插入一条记录时,至少要把这条记录的主键值记下来,之后回滚的时候值需要把这个主键值对应的记录删掉就好了。对于每个insert,Innodb 存储引擎都会完成一个 delete。
- 你删除了一条记录,至少要把这条记录中的内容都记下来,这样之后回滚时再把这些内容组成的记录插入到表中就好了。对于每个 delete,Innodb 存储引擎会执行一个 insert。
- 你修改了一条记录,至少要把修改这条记录前的旧值记录下来,这样之后回滚时再把这条记录更新为旧值就好了。对于每个 update,Innodb 存储会执行一个相反的 update,将修改前的行放回去。
MySQL把这些为了回滚而记录的这些内容称之为回滚日志,即 uodo log。由于查询操作并不会修改任何用户记录,所以在查询操作执行时,并不需要记录相应的 undo 日志。
此外,undo log 会产生 redo log,也就是 undo log 的产生会伴随 redo log 的产生,这是因为 undo log 也需要持久性的保护。
3. undo log 的作用
1. 回滚数据:
用户对undo log 可能有误解,以为 undo 用于将数据库物理的恢复到执行语句或事务之前的样子。但事实并非如此,undo是逻辑日志,因此只是将数据库逻辑的恢复到原来的样子。所有修改都被逻辑的取消了,但是数据结构和回滚之后可能大不相同。
这是因为在多并发系统中,可能会有数十、数百、甚至数千个并发事务。数据库的主要任务就是协调对数据记录的并发访问。比如,一个事务在修改当前一个页中某几条记录,同时还有别的事务在对同一个页中的另外几条记录进行修改。因此,不能将一个页回滚到事务开始的样子,因为这样会影响其他事务正在进行的工作。
2. MVCC:
undo 的另一个作用是MVCC,即在Innodb存储引擎中MVCC的实现是通过undo来完成的。当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可以通过读取之前的行版本信息,一次实现非锁定读。
4. undo 的存储结构
1. 回滚段与undo 页:
InnoDB 对 undo log 的管理采用段的方式,也就是 回滚段(rollback segment) 。每个回滚段记录了 1024 个 undo log segment ,而在每个undo log segment段中进行 undo页 的申请。
- 在 InnoDB1.1 版本之前,只有一个rollback segment,因此支持同时在线的事务 限制为 1024 。虽然对绝大多数的应用来说都已经够用。
- 从1.1版本开始 InnoDB 支持最大 128个rollback segment ,故其支持同时在线的事务限制提高到 了 128*1024 。
2. 回滚段与事务:
- 每个事务只会使用一个回滚段,一个回滚段在同一时刻可能会服务于多个事务。
- 当一个事务开始的时候,会制定一个回滚段,在事务进行的过程中,当数据被修改时,原始的数据会被复制到回滚段。
- 在回滚段中,事务会不断填充盘区,直到事务结束或所有的空间被用完。如果当前的盘区不够用,事务会在段中请求扩展下一个盘区,如果所有已分配的盘区都被用完,事务会覆盖最初的盘 区或者在回滚段允许的情况下扩展新的盘区来使用。
- 回滚段存在于undo表空间中,在数据库中可以存在多个 undo 表空间,但同一时刻只能使用一个 undo 表空间。
- 当事务提交时,InnoDB存储引擎会做以下两件事情: 将 undo log 放入列表中,以供之后的 purge 操作 判断undo log 所在的页是否可以重用,若可以分配给下个事务使用
3. 回滚段中的数据分类 :
- 未提交的回滚数据 (uncommitted undo information)
- 已经提交但未过期的回滚数据 (committed undo information)
- 事务已经提交并过期的数据 (expired undo information)
5. undo 的类型
在Innodb存储引擎中,undo log 分为:
1. insert undo log
insert undo log 是指在 insert 操作中产生中产生的 undo log。因为 insert 操作的记录,只对本事务可见,对其他事物不可见,故该 undo log可以在事务提交后直接删除。不需要进行 purge 操作。
2. update undo log
update undo log 记录的是对 delete 和 update 操作产生的 undo log。该 undo log 可能需要提供MVCC机制,因此不能再事务提交时救进行删除。提交时放入 undo log 链表,等待 purge 线程做最后的删除。
6. undo log 的生命周期
1. 简要生成过程
只有buffer pool的流程:

有了redo log和undo log之后:

在更新buffer pool中的数据之前,我们需要先将该数据事务开始之前的状态写入undo log 中。假设更新到一般出错了,我们就可以使用undo log来回滚到事务开始之前。
2. 详细生成过程
对于innodb存储引擎来说,每个行记录除了记录本身的数据之外,还有几个隐藏的列:
- db_row_id:如果没有为表显式的定义主键,并且表中页面有定义唯一索引,那么innodb会自动为表添加一个row_id的隐藏列作为主键。
- db_trx_id:每个事务都会分配一个事务id,当对某条记录发生变更时,就会将这个事务的id写入trx_id中。
- db_roll_ptr:回滚指针,本质上就是执行undo log的指针。

当我们执行insert时:
begin;
insert into user(name) values("tom");
插入的数据都会生成一条insert undo log,并且数据的回滚指针会指向它。undo log会记录undo log的序号,插入主键的列和值…,那么在进行rollback的时候,通过主键直接把对应的数据删除即可。

当我们执行update时:
对于更新的操作会产生update undo log,并且会分更新主键和不更新主键的,假设现在执行:
update user set name="sun" where id=1;

这时会把老的记录写入到新的undo log,让回滚指针执行新的undo log,它的undo no是1,并且性的undo log会指向老的undo log(undo no=0)。
假设现在执行:
update user set id=2 where id=1;

对于更新主键的操作,会先把原来的数据deletemark标识打开,这时并没有真正的删除数据,真正的删除会交给清理线程去判断,然后再后面插入一条心的数据,新的数据也会产生undo log,并且undo log的序号会递增。
可以发现,每次对数据的变更都会产生一个undo log,当一条记录被变更多次时,那么就会产生多条undo log,undo log记录的是变更前的日志,并且每个undo log的序号是递增的,那么当要回滚的时候,按照序号依次向前推,就可以找到我们的原始数据了。
3. undo log是如何回滚的
以上面的例子来说,假设执行rollback,那么对应的流程应该是这样:
1、 通过undono=3的日志把id=2的数据删除;
2、 通过undono=2的日志把id=1的数据的deletemark还原成0;
3、 通过undono=1的日志把id=1的数据的name还原成Tom;
4、 通过undono=0的日志把id=1的数据删除;
4. undo log的删除
针对于insert undo log :
因为insert操作的记录,只对事务本身可见,对其他事务不可见。故该undo log可以在事务提交后直接删 除,不需要进行purge操作。
针对于update undo log :
该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。
小结:

undo log 是逻辑日志,对事务回滚时,只是将数据库逻辑地恢复到原来的样子。
redo log 是物理日志,记录的是数据页的物理变化,undo log不是redo log的逆过程。