mysql-undolog

本文详细解释了InnoDB存储引擎如何通过undolog保证事务的ACID特性,涉及事务id的分配机制、undo日志的结构与操作,以及回滚段的设计。重点介绍了insert、delete和update操作对应的undolog记录和事务链的构建。
摘要由CSDN通过智能技术生成

一、原子性

ACID:通过undolog保证原子性

二、undolog 如何做

把回滚时所需的东西都给记下来:
1、插入一条记录时,至少要把这条记录的主键值记下来,回滚的时候只需要把这个主键值对应的记录删掉就好了。 
2、删除了一条记录,至少要把这条记录中的内容都记下来,回滚时再把由这些内容组成的记录插入 到表中就好了。 
2、修改了一条记录,至少要把修改这条记录前的旧值都记录下来,回滚时再把这条记录更新为旧值 就好了。

三、事务id

3.1 分配事务id时机

        某个事务执行过程中对某个表执行了增、删、改操作,那么 InnoDB 存储引擎就会给它分配一个独一无二的 事务id,如果只有读请求则不会分配事务id

3.2 事务id如何生成

1、服务器会在内存中维护一个全局变量,每当需要为某个事务分配一个 事务id 时,就会把该变量的值当作 事 务id 分配给该事务,并且把该变量自增1。
2、每当这个变量的值为 256 的倍数时,就会将该变量的值刷新到系统表空间的页号为 5 的页面中一个称之为 Max Trx ID 的属性处,这个属性占用 8 个字节的存储空间。
3、当系统下一次重新启动时,将上边提到的 Max Trx ID 属性加载到内存,该值加上256之后赋值给我们 前边提到的全局变量(因为在上次关机时该全局变量的值可能大于 Max Trx ID 属性值)。
***保证整个系统中分配的事务id是一个递增的数字。先被分配 id 的事务得到的是较小的事务id , 后被分配 id 的事务得到的是较大的 事务id 。***

3.3 trx_id隐藏列

trx_id 列其实还蛮好理解的,就是某个对这个聚簇索引记录做改动的语句所在的事务对应的 事务id 而已 (此处的改动可以是 INSERT 、 DELETE 、 UPDATE 操作)。

四、undo日志的格式 

4.1 insert对应的undolog

4.1.1 undolog日志结构

插入一条记录时有 乐观插入悲观插入 区分,对应TRX_UNDO_INSERT_REC 类型的undolog

 4.2.2 undolog记录实例

1、执行两个insert语句:

INSERT INTO undo_demo(id, key1, col) VALUES (1, 'AWM', '狙击枪'), (2, 'M416', '步枪');

2、第一条 undo日志 的 undo no 为 0 ,记录主键占用的存储空间长度为 4 ,真实值为 1 。画一个示意图就是 这样:

3、第二条undo日志 的 undo no 为 1 ,记录主键占用的存储空间长度为 4 ,真实值为 2 。画一个示意图就是 这样(与第一条 undo日志 对比, undo no 和主键各列信息有不同): 

4.2 DELETE对应的undo log

4.2.1 删除操作数据结构

Page Header 部分有一个称之为 PAGE_FREE 的属性,被删除的记录其实也会根据记录头信息中的 next_record 属性组成一个链表,只不过这个链表中的记录占用的存储空间可以被重新利用,所以也称这个链表 为 垃圾链表。

4.2.2 删除操作2个阶段 

1、阶段一:delete mark:仅仅将记录的 delete_mask 标识位设置为 1 ,其他的不做修改(其实会修改记录的 trx_id 、 roll_pointer 这些隐藏列的值),是一个 中间状态。

2、阶段二:purge 删除语句事务提交,有专门的线程后来真正的把记录删除掉。就 是把该记录从 正常记录链表 中移除,并且加入到 垃圾链表 中,还要调整一些页面的其他信息,比如页 面中的用户记录数量 PAGE_N_RECS 、上次插入记录的位置 PAGE_LAST_INSERT 、垃圾链表头节点的指针 PAGE_FREE 、页面中可重用的字节数量 PAGE_GARBAGE 、还有页目录的一些信息。

3、阶段二执行完成

将被删除记录加入到 垃圾链表 时,实际上加入到链表的头节点处,会跟着修改 PAGE_FREE 值。

4.2.3 删除操作日志结构

对应TRX_UNDO_DEL_MARK_REC类型的undolog,结构如下:

4.2.4 版本链

在对一条记录进行 delete mark 操作前,需要把该记录的旧的 trx_id 和 roll_pointer 隐藏列的值都给记 到对应的 undo日志 中来,对应图中的 old trx_id 和 old roll_pointer 属性。可以通过 undo日志 的 old roll_pointer 找到记录在修改之前对应的 undo 日志。比方说在一个事务 中,我们先插入了一条记录,然后又执行对该记录的删除操作,这个过程的示意图就是这样:

从图中可以看出来,执行完 delete mark 操作后,它对应的 undo log和 INSERT 操作对应的 undo 日志就串 成了一个链表。这个链表就称之为 版本链。

4.2.5 删除实例

BEGIN; # 显式开启一个事务,假设该事务的id为100
# 插入两条记录
INSERT INTO undo_demo(id, key1, col)
 VALUES (1, 'AWM', '狙击枪'), (2, 'M416', '步枪');
# 删除一条记录
DELETE FROM undo_demo WHERE id = 1;

这个 delete mark 操作对应的 undo日志 的结构就是这样: 


1、这条 undo 日志是 id 为 100 的事务中产生的第3条 undo 日志,所以它对应的 undo no 是 2 。 
2、在对记录做 delete mark 操作时,记录的 trx_id 隐藏列的值是 100 (也就是说对该记录最近的一次修改就 发生在本事务中),所以把 100 填入 old trx_id 属性中。然后把记录的 roll_pointer 隐藏列的值取出 来,填入 old roll_pointer 属性中,这样就可以通过 old roll_pointer 属性值找到最近一次对该记录做改 动时产生的 undo日志 。 
3、由于 undo_demo 表中有2个索引:一个是聚簇索引,一个是二级索引 idx_key1 。只要是包含在索引中的列,那么这个列在记录中的位置( pos ),占用存储空间大小( len )和实际值( value )就需要存储到 undo日志 中。 
    对于主键来说,只包含一个 id 列,存储到 undo日志 中的相关信息分别是:
    pos:id 列是主键,在记录的第一个列,它对应的 pos 值为 0 。 pos 占用1个字节来存储。 
    len : id 列的类型为 INT ,占用4个字节,所以 len 的值为 4 。 len 占用1个字节来存储。 
    value :在被删除的记录中 id 列的值为 1 ,也就是 value 的值为 1 。 value占用4个字节来存储。 画一个图演示一下就是这样:

对于 id 列来说,最终存储的结果是 ,存储这些信息占用的存储空间大小为 1 + 1 + 4 = 6 个字节。
对于 idx_key1 来说,只包含一个 key1 列,存储到 undo日志 中的相关信息分别是: 
    pos : key1 列是排在 id 列、 trx_id 列、 roll_pointer 列之后的,它对应的 pos 值为 3 。 pos 占用1个字节来存储。 
    len : key1 列的类型为 VARCHAR(100) ,使用 utf8 字符集,被删除的记录实际存储的内容是 AWM ,所以一共占用3个字节,也就是所以 len 的值为 3 。 len 占用1个字节来存储。 
    value :在被删除的记录中 key1 列的值为 AWM ,也就是 value 的值为 AWM 。 value 占用3个字节 来存储。

 key1 列来说,最终存储的结果是 ,存储这些信息占用的存储空间大小 为 1 + 1 + 3 = 5 个字节。

<0, 4, 1> 和 <3, 3, 'AWM'> 和 共占用 11 个字节。然后 index_col_info len 本身占用 2 个字节,所以加起来一共占用 13 个字节,把数字 13 就填到了 index_col_info len 的 属性中。

4.2.6 update操作对应undo log

        4.2.6.1 不更新主键

                1、 就地更新(占用空间不变)

                        每个列在更新前后占 用的存储空间一样大,直接在原记录基础上修改对应列的值。

                2、 占用空间改变

                        先删掉旧记录,再插入新纪录;要先把这条旧的记录从聚簇索引页面中删除掉,然后再根据更新后列的值创建一条新的记录插入到页面中。删除 并不是 delete mark 操作,而是真正的删除掉,也就是把这条记录从正常记录链表 中移除并加入到 垃圾链表中。如果新创建的记录占用的存储空间大小不超过旧记录占用的空间,那么可以直接重用被加入到垃圾链表中的旧记录所占用的存储空间,否则需要在页面中新申请一段空间以供新记录使用,如果本页面内没有可用的空间的话,那就需要进行页面分裂操作,然后再插入新记录。

        4.2.6.2 更新主键

                1、将旧记录进行 delete mark 操作

                2、根据更新后各列值创建新记录,将其插入到聚簇索引中(需重新定位插入的位置)。

4.3 roll pointer隐藏列的含义

占用 7 个字节的字段,本质上就是一个指向记录对应的 undo日志的指针 。

数据记录被存储到了类型为 FIL_PAGE_INDEX 的页面中(就是我们前边一直所说的 数据页 ), undo日志 被存放到了类型为 FIL_PAGE_UNDO_LOG 的页面中。

五、 undolog 页面结构

5.1 FIL_PAGE_UNDO_LOG页面 (undo页面)

        ​​​​​​

5.2 Undo页面链表

5.2.1 单个事务中的Undo页面链表

一个事务多个语句,形成TRX_UNDO_PAGE_NODE链表:

链表的第一个节点:first undo page

其他页面:normal undo page 

事务执行过程中,同一个 Undo页面 要么只存储 TRX_UNDO_INSERT 大类的 undo日志 ,要么只存储 TRX_UNDO_UPDATE 大类的 undo日志,事务执行过程中需要2个undo页面链表,一个是insert undo链表,一个是update undo链表:

记录表和临时表产生undo log,形成4个链表:

***按需分配,啥时候需要啥时候再分配,不需要就不分配***

 5.2.2 多个事务undolog 链表

***不同事务执行过程中产生的undo日志需要被写入到不同的Undo页面链表中***

trx 1 对普通表做了 DELETE 操作,对临时表做了 INSERT 和 UPDATE 操作。
    InnoDB 会为 trx 1 分配3个链表,分别是:
    针对普通表的 update undo链表 。
    针对临时表的 insert undo链表 。
    针对临时表的 update undo链表 。
trx 2 对普通表做了 INSERT 、 UPDATE 和 DELETE 操作,没有对临时表做改动。
    InnoDB 会为 trx 2 分配2个链表,分别是:
    针对普通表的 insert undo链表 。
    针对普通表的 update undo链表 。

5.3 小结

对于没有被重用的 Undo页面 链表来说,链表的第一个页面,也就是 first undo page 在真正写入 undo日志前,会填充 Undo Page Header 、 Undo Log Segment Header 、 Undo Log Header 这3个部分,之后才开始正式 写入 undo日志 。对于 normal undo page 页面在真正写入 undo日志 前,只会填充 Undo Page Header 。链表的 List Base Node 存放到 first undo page 的 Undo Log Segment Header 部分, List Node 信息存放到每一个 Undo页面 的 undo Page Header 部分,所以画一个 Undo页面 链表的示意图就是这样:

 六、回滚段

设计新增页面类型为:Rollback Segment Header的页面,存储Undo页面链表的 frist undo page页号 ,他们把这些页号称之为 undo slot。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值