MySQL

洞悉MySQL底层架构与SQL调优本质
帅旋
关注
充电
IT宅站长,技术博主,架构师,全网id:arthinking。

InnoDB执行引擎内幕:Undo Log

发布于 2020-05-30 | 更新于 2024-05-16

上面说的redo log记录了事务的行为,可以通过其对页进行重做操作,但是食物有时候需要进行回滚,这时候就需要undo log了[1]

**关于Undo Log的存储:**InnoDB中有回滚段(rollback segment),每个回滚段记录1024个undo log segment,在每个undo log segment段中进行申请undo页。系统表空间偏移量为5的页记录了所有的rollback segment header所在的页。

image-20200530123839227

1、undo log的格式

根据行为不同分为两种:

insert undo log

insert undo log:只对事务本身可见,所以insert undo log在事务提交后可直接删除,无需执行purge操作;

insert undo log主要记录了:

next 记录下一个undo log的位置
type_cmpl undo的类型:insert or update
*undo_no 记录事务的ID
*table_id 记录表对象
*len1, col1 记录列和值
*len2, col2 记录列和值
start 记录undo log的开始位置

假设在事务1001中,执行以下sql,t20的table_id为10:

1
insert into t20(id, a, b, c, d) values(12, 2, 3, 1, "init")

那么对应会生成一条undo log:

image-20200530165013967

update undo log

update undo log:执行update或者delete会产生undo log,会影响已存在的记录,为了实现MVCC(后边介绍),update undo log不能再事务提交时立刻删除,需要将事务提交时放入到history list上,等待purge线程进行最后的删除操作。

update undo log主要记录了:

next 记录下一个undo log的位置
type_cmpl undo的类型:insert or update
*undo_no undo日志编号
*table_id 记录表对象
info_bits
*DATA_TRX_ID 事务的ID
*DATA_ROLL_PTR 回滚指针
*len1, i_col1 n_unique_index
*len2, i_col2
n_update_fields 以下是update vector信息,表示update操作导致发送改变的列
*pos1, *len1, u_old_col1
*pos2, *len2, u_old_col2
n_bytes_below
*pos, *len, col1
*pos, *len, col2
start 记录undo log的开始位置

假设在事务1002中,执行以下sql,t20的table_id为10:

1
update t20 set d="update1" where id=60;

那么对应会生成一条undo log:

image-20200530171944498

如上图,每回退应用一个undo log,就回退一个版本,这就是MVCC(Multi versioning concurrency control)的实现原理。

下面我们在执行一个delete sql:

1
delete from t20 where id=60;

对应的undo log变为如下:

image-20200530172640974

如上图,实际的行记录不会立刻删除,而是在行记录头信息记录了一个deleted_flag标志位。最终会在purge线程purge undo log的时候进行实际的删除操作,这个时候undo log也会清理掉。

2、MVCC实现原理

如上图所示,MySQL只会有一个行记录,但是会把每次执行的sql导致行记录的变动,通过undo log的形式记录起来,undo log通过回滚指针连接在一起,这样我们想回溯某一个版本的时候,就可以应用undo log,回到对应的版本视图了。

我们知道InnoDB是支持RC(Read Commit)和RR(Repeatable Read)事务隔离级别的,而这个是通过一致性视图(consistent read view)实现的。

一个事务开启瞬间,所有活跃的事务(未提交)构成了一个视图数组,InnoDB就是通过这个视图数组来判断行数据是否需要undo到指定的版本:

image-20200530213342612

RR事务隔离级别

假设我们使用了RR事务隔离级别。我们看个例子:

如下图,假设id=60的记录a=1

image-20200530215747142

事务C启动的瞬间,活跃的事务如下图黄色部分所示:

image-20200530222622858

也就是对于事务A、事务B、事务C,他们能够看到的数据只有是行记录中的最大事务IDDATA_TRX_ID<=11的,如果大于,那么只能通过undo进行回滚了。如果TRX_ID=当前事务id,也可以看到,即看到自己的改动。

另外有一个需要注意的:

  • 在RR隔离级别下,当事务更新事务的时候,只能用当前读来获取最新的版本数据来更新,如果当前记录的行锁被其他事务占用,就需要进入所等待;
  • 在RC隔离级别下,每个语句执行都会计算出新的一致性视图。

所以我们分析上面的例子的执行流程:

  • 事务C执行update,执行当前读,拿到的a=1,然后+1,最终a=2,同时添加一个TRX_ID=11的undo log;
    • image-20200530221027891
  • 事务B执行select,使用快照读,记录的DATA_TRX_ID > 11,所以需要通过undo log回滚到DATA_TRX_ID=11的版本,所以拿到的a是1;
  • 事务B执行update,需要使用当前读,拿到最新的记录,a=2,然后加1,最终a=3;
    • image-20200530221734284
  • 事务B执行select,拿到当前最新的版本,为自己的事务id,所以得到a=3;
  • 事务A执行select,使用快照读,记录的DATA_TRX_ID > 11,所以需要通过undo log回滚到DATA_TRX_ID=11的版本,所以拿到的a是1。
  • 如果是RC隔离级别,执行select的时候会计算出新的视图,新的视图能够看到的最大事务ID=14,由于事务B还没提交,事务C提交了,所以可以得到a=2:
    • image-20200530223051094

References


  1. 姜承尧. MySQL技术内幕-InnoDB存储引擎第二版[M]. 机械工业出版社, 2013-5:306. ↩︎

本文作者: 帅旋

本文链接: https://www.itzhai.com/columns/mysql/innodb/undo-log.html

版权声明: 版权归作者所有,未经许可不得转载,侵权必究!联系作者请加公众号。

×
IT宅

关注公众号及时获取网站内容更新。

请帅旋喝一杯咖啡

咖啡=电量,给帅旋充杯咖啡,他会满电写代码!

IT宅

关注公众号及时获取网站内容更新。