MySQL的undo日志原理

很多关于undo日志的文章都写得很好,但由于undo日志本身原理繁杂,容易混乱,故写此文用于梳理

一、undo日志是干什么的?

用于事务回滚,保证事务的原子性

二、必须要考虑的事:

1.undo日志要记什么内容?

2.undo日志在事务提交前,事务回滚后,事务提交后,状态是怎样的?

3.如何对undo日志进行分类和管理?

 

首先,来解决第一个问题:记什么?

undo日志实际上应该记一个操作。事务中执行了什么,我就反向做什么。比如事务中插入了一个数据,我就要记住插入数据的id以便反向删除;事务更新了一个数据,我就要记住事务更新前的数据是什么,以便恢复数据。

但是,又有个问题。我怎么知道这条undo日志就是这个事务的呢?

因此,undo日志中,还必须记下trx-id,即事务id

然后,我们还发现,一个事务中可能会对多张表的数据进行增删改。undo日志就只记下一个反向操作所需的数据,但是没告诉我是操作谁啊!

于是,undo日志中,还必须记下table-no,操作表的id

事实上,undo日志中还要记很对其它信息,这里就不一一列举了。

# 直接来看insert类型undo日志的结构:

8ca4812965f3492bb5a67dbcbcf28017.png

 诶,等等,trx-id去哪了呢?其实我们仔细想想, 不同事务的undo日志可能存放在一起吗?如果可以,岂不是乱套了!那么到底在哪里记录trx-id呢?稍安勿躁!后面自会揭晓。

从上图可以看到,insert类型的undo日志中,【主键各列信息】这一项就是用来删除插入记录的,符合我们开始的预想。

# 再来看看delete类型的undo日志:

151d3e595fac45ce9bf8235a0fd85f32.png

 嗯!怎么这里又冒出来trx-id了呢?而且打上了一个old的修饰。事实上,这里的old trx_id是为了一个叫MVCC的特性服务的,这里可以暂且不理它。

注意,这个删除日志记录的【删除记录原本的数据】并不是用来在事务回滚时,重新插入旧记录的!你会发现它只是记录了删除记录原本的主键值,并没有把其它字段的值完整记录下来。

事实上,在事务提交之前,你以为你真的把记录删掉了吗?并没有。真正的删除会等到你提交事务后,事务会根据delete的undo日志去执行实际的删除。这样做的好处是显然易见的:减少了delete日志需要记录的内容。

完整的过程是这样的:

①在你未提交事务时,你删除的记录只会打上一个删除标记,但是由于该记录没有移到垃圾链表(别告诉我你连垃圾链表都不知道是什么),该记录仍然可以被其它事务访问,而不可以被其它数据重用。(此外,还会修改old_trx_id和old roll_pointer的值,但这并不是重点)

79502b6d99754d94876154c16455a2b0.png

 a9ecacc1c6ad466c93e653b9b7342ae2.png

 

②如果你提交了事务,那么只需要把这个删除标记改回来就好。

③如果你回滚了事务,那么就要根据undo日志执行实际的删除,这一阶段称之为purge(净化)。

# 最后来看看update类型的undo日志

首先要清楚,在事务提交前,事务的更新就已经完成了。但这一更新过程是有明显差别的。

更新的方式主要分为三类:

ac81d56be36b481eac510639ed584946.jpg

 对于不更新主键的情况,①如果说修改后每个字段和原来的字段占用空间都一样,就可以“原地更新”,直接覆盖掉旧记录的内容;②如果说修改后又任何一个字段占用空间和原来不同,就不会采用就地更新的方法,而是先将旧记录删除并假如垃圾链表,而后直接申请一块新页面将新数据写入;

不更新主键的情况会产生如下的undo日志:

e100ca93c29342d28536638b3e4950ab.png

 我们只需要关注两条信息:①被更新列更新前的信息、②凡是被索引的列的各列信息

在事务回滚后,根据②找到修改前数据的位置,根据①将数据改回修改前的样子

【仔细观察会发现,上图的old roll-pointer指向了一条undo日志。这其实就是版本链,但这不是重点,只需有个印象即可】

【tips:一条记录删除并移入垃圾链表,此时它就是垃圾链表的头节点。新插入记录不会直接在页面空闲区域插入,而是会先检查垃圾链表的头节点。如果头节点的记录空间比自己要插入记录空间大,那么这块区域就会被重用;否则,再插入到空闲区域】

对于更新主键的情况来说,由于更新主键后,有可能由于主键值和原来差得太对,记录都不在原来的页面了,因此对比【不更新主键②】来说,在删除旧记录后,新纪录的插入就需要依靠聚簇索引来定位应该插入到的页面。

更新主键的情况会生成两条undo日志,一条delete类型的undo日志,一条insert类型的undo日志。

总结一下:

47bd9e709c7b4a4292010eafe956d64c.png

 

接着,我们来探讨如何对undo日志进行分类。

上面其实我们已经将undo日志的页面做了分类了。但仅仅将undo日志页面分类,就够了吗?显然不够。

比如,一个insert类型的undo页面就能将所有insert类型的undo日志记录完吗?肯定不行嘛!那就会有很多insert类型的undo页面,这些页面是不是应该串成链表?

在MySQL中,链表结构总是用来进行分类。每一个链表节点都有这样相似的结构:

1febc889758645b4b702de97b28d199a.png

 简而言之,就是有一个双向指针。

而链表基节点(就是用来管理链表结构的)结构如图:

bcc9ec542ce94986b385b97310651459.png

 最左侧的就是链表基节点

 

我们可以将insert类型,detete类型和update类型的undo页面串成链表。

但这还不够。

我们是根据事务进行数据回滚的,自然而然就应该以事务id为标准进行分类啊!

因此,链表的分配是这样的:一个事务执行时,按需分配。比如事务中执行了一个insert操作,那么就会分配一个insert undo页面链表。如果执行了一个update主键的操作,就会分配一个insert undo页面链表以及一个delete undo页面链表。

既然链表是按事务进行管理的,自然就应该在每个链表的头节点处记录下它的事务id。事实上,这个信息就记录在链表头节点的Undo Log Header里面。

c50a461cd2834b49b7eb206ede71dded.jpg

在Undo Log Header里头还有一个标记事务内部子事件的执行顺序。为什么要记录这个顺序呢?这是因为事务执行是有序的,我们在回滚时应该就按照这个顺序的相反方向进行回滚。

总的来说,Undo Log Header就是用来管理链表事务的。【一组】=【一个事务 】

链表头节点中还有一个Undo log segment header,里面的segment Header是指向段属性的指针。段是什么?你不会不知道吧?简而言之就是一些为了同一个目而存在的一片连续的页。段能够很好的管理这些连续的页。每个链表都会对应一个段,链表中的页面都是从这个段中申请的。

【其实,段是一些零散的页和完整的区的集合。但这里为了方便理解,就这么说了。】

 

# undo日志是怎么组织存储的?

上面说了一堆不同事务的链表,它们又是怎么组织存储的呢?

直接来看:

5329618f5360403a8e4abcd5da7fc4b4.png

好家伙,那么抽象!我们从下往上看。

我们做一个想象:undo链表就像一个班级,而undo链表的头节点就是班长。通过班长就能很快的找到对应的班级。这里undo slot就是undo链表头节点的页号。哦!原来这一个个undo slot就是“班长”啊!班长那么多,怎么集中起来?当然是通过会议室--rollback segment header(回滚段)来集中啦!会议室装不下那么多“班长”在那么办?再多建几个“会议室”呗!多个“会议室”的地址就放在了系统表空间的第五号页面中。

我们怎样为一个事务分配对应的链表呢?先卖个关子,我们先来想想,事务提交后,undo日志还有用吗?

我们说,undo日志是用来在事务回滚后进行数据恢复的。事务提交了,自然就没有回滚的需求了,理应删除掉这个undo日志啊!事实上,由于MVCC特性(😡😡怎么又是他),只有insert类型的undo日志会在事务提交后释放掉,而update,deletr类型的undo类型日志不会被删掉,而是加入到版本链中,接着为MVCC服务。

我们来看看undo日志的释放过程:

【重用指的是对undo链表的重新利用,如果一个undo页面链表只有一个页面(链表头节点)且页面占用空间<75%,那么这个链表就会被重用。】

81865ca7964341fd8edf5f7d148e5840.png

我猜你肯定还有点疑惑,再来看我们怎样为一个事务分配对应的链表:

70d370e6d9004fe6af5840cf538abb1e.png

 

看不懂就多看几遍上面两张图,不多解释。

🤔🤔好像还有一些细节,太多了 ,就不多一一列举了。核心内容就这些!

 

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL事务实现的原理是通过ACID(原子性、一致性、隔离性和持久性)特性来保证数据的一致性和可靠性。 具体实现原理如下: 1. 原子性(Atomicity):事务是一个原子操作单元,要么全部执行成功,要么全部失败回滚。MySQL通过日志(Redo Log和Undo Log)来实现原子性。在事务执行期间,将所有的修改操作都写入Redo Log中,如果发生了错误或者回滚操作,可以根据Redo Log中的记录重新执行或者回滚事务。 2. 一致性(Consistency):事务执行前后,数据库从一个一致的状态转换到另一个一致的状态。MySQL通过在事务开始前和结束后进行锁定和解锁操作来实现一致性。在事务开始前,会对相关的数据进行锁定,防止其他事务的修改操作;在事务结束后,会释放这些锁。 3. 隔离性(Isolation):多个事务并发执行时,每个事务都应该感觉不到其他事务的存在。MySQL通过锁机制来实现隔离性。在并发执行的情况下,对于读操作(如SELECT),可以使用共享锁(Shared Lock)来允许多个事务同时读取同一份数据;对于写操作(如UPDATE、DELETE、INSERT),需要使用排他锁(Exclusive Lock)来防止其他事务的读写操作。 4. 持久性(Durability):一旦事务提交成功,其结果就应该永久保存在数据库中。MySQL通过将事务操作记录写入磁盘的方式来实现持久性。当事务提交后,会将Redo Log中的修改操作应用到磁盘上的数据文件中,确保数据的持久保存。 综上所述,MySQL通过日志记录、锁机制和数据持久化等方式来实现事务的原子性、一致性、隔离性和持久性。这些机制保证了数据库的数据操作的可靠性和一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值