一、rollback segment

提到了undo log,就不得不说roll back segment了。
InnoDB存储引擎会先初始化好rollback segment(回滚段),在每个回滚段中会记录N个undo log segment,而我们说的undo log就是在undo log segment中申请出来的!
在早期的InnoDB版本中只有一个rollback segment,因此它支持的并发事务的上限是1024个。
在MySQL5.7中回滚段已经支持到了128个(上限是128)。其中32个分配给临时表空间。剩下的96个回滚段可以分配给修改常规表中数据的事务。可以通过参数innodb_rollback_segments调整回滚段的数量。
另外,上面提到的“每个回滚段中都记录了N个undolog segment”, 这里的N和数据页大小有关:

InnoDB页面大小 回滚段中的撤消插槽数(InnoDB页面大小/ 16)
4096 (4KB) 256
8192 (8KB) 512
16384 (16KB) 1024
32768 (32KB) 204
65536 (64KB) 4096

二、undo log truncate

结合 truncate table sql,就能更好的理解这个概念。当你不需要某个表中的数据时,你可以执行truncate sql将表中的数据清空掉。同样的undo log的truncate机制本质上就是为undo log 表空间文件瘦身,将不需要的undo log清理掉。
在MySQL 5.6(包括5.6)之前undo tablespace里面的undo数据文件是无法收缩的。也就是说在实例的运行过程中如果遇到有大的事务,会把undo log的文件撑的非常大。浪费大量的空间甚至会把磁盘打爆,同时也增加了数据库物理备份的时间。
在MySQL5.7中允许用户在线truncate undo log。

2.1 undo log truncate实现

前提:必须使用独立的undo表空间,然后配合如下的参数辅助:
undo log和事务回滚 - 图1
创建数据表:

  1. create table test (
  2. id int primary key auto_increment,
  3. name varchar(64)
  4. );

然后不断的往这个测试表中插入数据

insert into test(name) values(repeat('a',64));
insert into test(name) select name from test;

一边插入一边ll -lh观察undo 表空间文件的变化:你会发现有个undo00X表空间文件已经超过了参数:innodb_max_undo_log_size=100M 指定的范围,意味着这个undolog已经被标记为可回收了。

当事务提交时,undo log并不会被立即删除,因为可能存在其它的事务需要使用undo log将数据回滚到之前的版本。最终是否可以删除undo log由purge线程决定。
为了让pruge线程运行,可以执行如下的sql:

delete from test limit 1;

执行完后,undo log会瘦身成功。

三、undo log

undo log有两种类型,分别是 insert undo log 和 update undo log。

3.1 insert undo log

insert 类型的sql,会在undo log中记录下方才你insert 进来的数据的ID,根据ID完成精准的删除。
undo log和事务回滚 - 图2

3.2 update undo log

undo log和事务回滚 - 图3

四、事务是如何回滚的?(undo log 链条)

4.1 insert undo log 链条

对于 insert 类型的sql,会在undo log中记录方才你insert进来的数据的ID,当你想roll back时,根据ID完成精准的删除。
undo log和事务回滚 - 图4
有一个注意点:因为单纯的insert sql不涉及多MVCC的能力。所以一旦事务commit,这条insert undo log就可以直接删除了。

4.2 update undo log 链条

4.2.1 delete类型的sql

对于delete类型的sql,会在undo log中记录方才你删除的数据,当回滚时会将删除前的数据insert。

4.2.2 update类型的sql

对于update类型的sql,会在undo log中记录修改前的数据,回滚时只需要反向update即可。
一个事务A开启后插图了一条记录:name = tom,MySQL会记录下这样一条undo log
undo log和事务回滚 - 图5
随后先后来了两个事务:

  • 事务B,事务ID=61,它执行sql将name 改成jerry。
  • 事务C,事务ID=62,它执行sql将name 改成tom。

于是MySQL记录下这样一条新的undo log:
undo log和事务回滚 - 图6
可以看到,MySQL会将对一行数据的修改undo log通过DATA_ROLL_ID指针连接在一起形成一个undo log链表链条。这样事务C如果想回滚,他会将数据回滚到事务B修改后的状态。而事务B想回滚他会将数据回滚到事务A的状态。

4.3 回滚是逻辑层面的

表空间、数据页存在于物理层面。SQL想要修改的数据表、id=xxx的行都是逻辑上的。
undo log 帮你做的是逻辑上的数据回滚,而不是物理(数据页)上是数据回滚。

补充

在MySQL5.6、MySQL5.7版本中可以通过innodb_undo_tablespaces参数配置redo log表空间文件的个数,但是官网也有介绍这个参数在未来的MySQL版本中将会被废弃,在MySQL8.0中初始化MySQL实例时会创建两个默认的撤消表空间,并且可以使用CREATE UNDO TABLESPACE语法创建其他撤消表空间 。

但是不管怎么样,如果你使用的是MySQL5.7还是推荐使用这些参数以及开启undo log的自动truncate。