当期文章MySQL · 引擎特性 · 庖丁解InnoDB之UNDO LOG

Undo Log是InnoDB十分重要的组成部分,它的作用横贯InnoDB中两个最主要的部分,并发控制(Concurrency Control)和故障恢复(Crash Recovery),InnoDB中Undo Log的实现上亦日志亦数据。本文将从其作用、设计思路、记录内容、组织结构,以及各种功能实现等方面,整体介绍InnoDB中的Undo Log,文章会深入一定的代码实现,但在细节上还是希望用抽象的实现思路代替具体的代码。本文基于MySQL 8.0,但在大多数的设计思路上MySQL的各个版本都是一致的。考虑到篇幅有限,以及避免过多信息的干扰,从而能够聚焦Undo Log本身的内容,本文中一笔带过或有意省略了一些内容,包括索引、事务系统、XA事务、Virtual Column、外部记录、Blob等。

Undo Log的作用

数据库故障恢复机制的前世今生中提到过,Undo Log用来记录每次修改之前的历史值,配合Redo Log用于故障恢复。这也就是InnoDB中Undo Log的第一个作用:

1. 事务回滚

在设计DB时,我们假设数据库可能在任何时刻,由于如硬件故障,软件Bug,运维操作等原因突然崩溃。这个时候尚未完成提交的事务可能已经有部分数据写入了磁盘,如果不加处理,会违反数据库对Atomic的保证,也就是任何事务的修改要么全部提交,要么全部取消。针对这个问题,直观的想法是等到事务真正提交时,才能允许这个事务的任何修改落盘,也就是No-Steal策略。显而易见,这种做法一方面造成很大的内存空间压力,另一方面提交时的大量随机IO会极大的影响性能。因此,数据库实现中通常会在正常事务进行中,就不断的连续写入Undo Log,来记录本次修改之前的历史值。当Crash真正发生时,可以在Recovery过程中通过回放Undo Log将未提交事务的修改抹掉。InnoDB采用的就是这种方式。

既然已经有了在Crash Recovery时支持事务回滚的Undo Log,自然地,在正常运行过程中,死锁处理或用户请求的事务回滚也可以利用这部分数据来完成。

2. MVCC(Multi-Versioin Concurrency Control)

浅析数据库并发控制机制中提到过,为了避免只读事务与写事务之间的冲突,避免写操作等待读操作,几乎所有的主流数据库都采用了多版本并发控制(MVCC)的方式,也就是为每条记录保存多份历史数据供读事务访问,新的写入只需要添加新的版本即可,无需等待。InnoDB在这里复用了Undo Log中已经记录的历史版本数据来满足MVCC的需求。

什么样的Undo Log

庖丁解InnoDB之REDO LOG中讲过的基于Page的Redo Log可以更好的支持并发的Redo应用,从而缩短DB的Crash Recovery时间。而对于Undo Log来说,InnoDB用Undo Log来实现MVCC,DB运行过程中是允许有历史版本的数据存在的。因此,Crash Recovery时利用Undo Log的事务回滚完全可以在后台,像正常运行的事务一样异步回滚,从而让数据库先恢复服务。因此,Undo Log的设计思路不同于Redo Log,Undo Log需要的是事务之间的并发,以及方便的多版本数据维护,其重放逻辑不希望因DB的物理存储变化而变化。因此,InnoDB中的Undo Log采用了基于事务的Logical Logging**的方式。

同时,更多的责任意味着更复杂的管理逻辑,InnoDB中其实是把Undo当做一种数据来维护和使用的,也就是说,Undo Log日志本身也像其他的数据库数据一样,会写自己对应的Redo Log,通过Redo Log来保证自己的原子性。因此,更合适的称呼应该是Undo Data

Undo Record中的内容

每当InnoDB中需要修改某个Record时,都会将其历史版本写入一个Undo Log中,这种Undo Record是Update类型。当插入新的Record时,还没有一个历史版本,但为了方便事务回滚时做逆向(Delete)操作,这里还是会写入一个Insert类型的Undo Record。

Insert类型的Undo Record

这种Undo Record在代码中对应的是TRX_UNDO_INSERT_REC类型。不同于Update类型的Undo Record,Insert Undo Record仅仅是为可能的事务回滚准备的,并不在MVCC功能中承担作用。因此只需要记录对应Record的Key,供回滚时查找Record位置即可。

insert_undo_record.png

其中Undo Number是Undo的一个递增编号,Table ID用来表示是哪张表的修改。下面一组Key Fields的长度不定,因为对应表的主键可能由多个field组成,这里需要记录Record完整的主键信息,回滚的时候可以通过这个信息在索引中定位到对应的Record。除此之外,在Undo Record的头尾还各留了两个字节用户记录其前序和后继Undo Record的位置。

Update类型的Undo Record

由于MVCC需要保留Record的多个历史版本,当某个Record的历史版本还在被使用时,这个Record是不能被真正的删除的。因此,当需要删除时,其实只是修改对应Record的Delete Mark标记。对应的,如果这时这个Record又重新插入,其实也只是修改一下Delete Mark标记,也就是将这两种情况的delete和insert转变成了update操作。再加上常规的Record修改,因此这里的Update Undo Record会对应三种Type:TRX_UNDO_UPD_EXIST_REC、TRX_UNDO_DEL_MARK_REC和TRX_UNDO_UPD_DEL_REC。他们的存储内容也类似:

update_undo_record.png

除了跟Insert Undo Record相同的头尾信息,以及主键Key Fileds之外,Update Undo Record增加了:

  • Transaction Id记录了产生这个历史版本事务Id,用作后续MVCC中的版本可见性判断
  • Rollptr指向的是该记录的上一个版本的位置,包括space number,page number和page内的offset。沿着Rollptr可以找到一个Record的所有历史版本。
  • Update Fields中记录的就是当前这个Record版本相对于其之后的一次修改的Delta信息,包括所有被修改的Field的编号,长度和历史值。

专业开发者搜索引擎「只收录经典」

  • 35
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值