一、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表空间,然后配合如下的参数辅助:
创建数据表:
create table test (id int primary key auto_increment,name varchar(64));
然后不断的往这个测试表中插入数据
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完成精准的删除。
3.2 update undo log

四、事务是如何回滚的?(undo log 链条)
4.1 insert undo log 链条
对于 insert 类型的sql,会在undo log中记录方才你insert进来的数据的ID,当你想roll back时,根据ID完成精准的删除。
有一个注意点:因为单纯的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
随后先后来了两个事务:
- 事务B,事务ID=61,它执行sql将name 改成jerry。
- 事务C,事务ID=62,它执行sql将name 改成tom。
于是MySQL记录下这样一条新的undo log:
可以看到,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。
