之前我们已经给大家深入讲解了在执行增删改操作时候的redo log的重做日志原理,其实说白了,就是你对buffer pool里的缓存页执行增删改操作的时候,必须要写对应的redo log记录下来你做了哪些修改

    如下图所示,redo log都是先进入redo log buffer中的一个block,然后事务提交的时候就会刷入磁盘文件里去。
    如果事务执行到一半要回滚怎么办?再探undo log回滚日志原理! - 图1
    这样万一要是你提交事务了,结果事务修改的缓存页还没来得及刷入磁盘上的数据文件,此时你MySQL关闭了或者是宕机了,那么buffer pool里被事务修改过的数据就全部都丢失了!

    但是只要有redo log,你重启MySQL之后完全是可以把那些修改了缓存页,但是缓存页还没来得及刷入磁盘的事务,他们所对应的redo log都加载出来,在buffer pool的缓存页里重做一遍,就可以保证事务提交之后,修改的数据绝对不会丢!

    相信之前讲解了redo log日志之后,大家对这块都理解的更加深刻了,那么今天我们就带着大家来探索另外一种日志,就是undo log日志,也就是回滚日志,这种日志要应对的场景,就是事务回滚的场景!

    那么首先大家先思考一个问题,假设现在我们一个事务里要执行一些增删改的操作,那么必然是先把对应的数据页从磁盘加载出来放buffer pool的缓存页里,然后在缓存页里执行一通增删改,同时记录redo log日志,对吧?如下图。
    如果事务执行到一半要回滚怎么办?再探undo log回滚日志原理! - 图2
    但是现在问题来了,万一要是一个事务里的一通增删改操作执行到了一半,结果就回滚事务了呢?

    比如一个事务里有4个增删改操作,结果目前为止已经执行了2个增删改SQL了,已经更新了一些buffer pool里的数据了,但是还有2个增删改SQL的逻辑还没执行,此时事务要回滚了怎么办?看图
    如果事务执行到一半要回滚怎么办?再探undo log回滚日志原理! - 图3
    这个时候就很尴尬了,如果你要回滚事务的话,那么必须要把已经在buffer pool的缓存页里执行的增删改操作给回滚了

    但是怎么回滚呢?毕竟无论是插入,还是更新,还是删除,该做的都已经做了啊!

    所以在执行事务的时候,才必须引入另外一种日志,就是undo log回滚日志

    这个回滚日志,他记录的东西其实非常简单,比如你要是在缓存页里执行了一个insert语句,那么此时你在undo log日志里,对这个操作记录的回滚日志就必须是有一个主键和一个对应的delete操作,要能让你把这次insert操作给回退了。

    那么比如说你要是执行的是delete语句,那么起码你要把你删除的那条数据记录下来,如果要回滚,就应该执行一个insert操作把那条数据插入回去。

    如果你要是执行的是update语句,那么起码你要把你更新之前的那个值记录下来,回滚的时候重新update一下,把你之前更新前的旧值给他更新回去。

    如果你要是执行的是select语句呢?不好意思,select语句压根儿没有在buffer pool里执行任何修改,所以根本不需要undo log!

    好,所以我们来看下图,其实你在执行事务期间,之前我们最开始的几篇文章就讲过,你除了写redo log日志还必须要写undo log日志,这个undo log日志是至关重要的,没有他,你根本都没办法回滚事务!
    如果事务执行到一半要回滚怎么办?再探undo log回滚日志原理! - 图4
    明天我们继续来看看insert、delete和update几种操作的undo log到底长什么样,相信大家看完了,就会对undo log这块机制有一个更加深刻的理解了。


    昨天我们讲解了undo log回滚日志的作用,说白了,就是你执行事务的时候,里面很多INSERT、UPDATE和DELETE语句都在更新缓存页里的数据,但是万一事务回滚,你必须有每条SQL语句对应的undo log回滚日志,根据回滚日志去恢复缓存页里被更新的数据。

    比如你执行了INSERT语句,那么你的undo log必须告诉你插入数据的主键ID,让你在回滚的时候可以从缓存页里把这条数据给删除了;

    如果你执行了DELETE语句,那么你的undo log必须记录下来被删除的数据,回滚的时候就得重新插入一条数据;

    如果你执行了UPDATE语句,那么你必须记录下来修改之前的数据,回滚的时候就得把数据给更新回去,如下图所示。
    如果事务执行到一半要回滚怎么办?再探undo log回滚日志原理! - 图5
    那么今天我们就一起来看看这个INSERT语句的undo log日志到底长什么样子呢?

    INSERT语句的undo log的类型是TRX_UNDO_INSERT_REC,这个undo log里包含了以下一些东西:

    • 这条日志的开始位置
    • 主键的各列长度和值
    • 表id
    • undo log日志编号
    • undo log日志类型
    • 这条日志的结束位置

    接下来我们来给大家解释一下,首先,一条日志必须得有自己的一个开始位置,这个没什么好说的是吧?

    那么主键的各列长度和值是什么意思?大家都知道,你插入一条数据,必然会有一个主键!

    如果你自己指定了一个主键,那么可能这个主键就是一个列,比如id之类的,也可能是多个列组成的一个主键,比如“id+name+type”三个字段组成的一个联合主键,也是有可能的。

    所以这个主键的各列长度和值,意思就是你插入的这条数据的主键的每个列,他的长度是多少,具体的值是多少。即使你没有设置主键,MySQL自己也会给你弄一个row_id作为隐藏字段,做你的主键。

    接着是表id,这个就不用多说了,你插入一条数据必然是往一个表里插入数据的,那当然得有一个表id,记录下来是在哪个表里插入的数据了。

    undo log日志编号,这个意思就是,每个undo log日志都是有自己的编号的。

    而在一个事务里会有多个SQL语句,就会有多个undo log日志,在每个事务里的undo log日志的编号都是从0开始的,然后依次递增。

    至于undo log日志类型,就是TRX_UNDO_INSERT_REC,insert语句的undo log日志类型就是这个东西。

    最后一个undo log日志的结束位置,这个自然也不用多说了,他就是告诉你undo log日志结束的位置是什么。

    那么接着我们用一个图画一下这个INSERT语句的undo log回滚日志的结构,大家来看一眼,感受一下。
    如果事务执行到一半要回滚怎么办?再探undo log回滚日志原理! - 图6
    大家可以想象一下,有了这条日志之后,剩下的事儿就好办了

    万一要是你现在在buffer pool的一个缓存页里插入了一条数据了,执行了insert语句,然后你写了一条上面的那种undo log,现在事务要是回滚了,你直接就把这条insert语句的undo log拿出来。

    然后在undo log里就知道在哪个表里插入的数据,主键是什么,直接定位到那个表和主键对应的缓存页,从里面删除掉之前insert语句插入进去的数据就可以了,这样就可以实现事务回滚的效果了!

    好了,今天先初步的看一下insert语句的undo log回顾日志,delete语句和update语句的回滚日志我们暂时就不细讲了,其实大家应该都能想象到他们是如何实现的。