第22章 后悔了怎么办-undo日志(上)
事物回滚的需求
事务保持原子性------一不做二不休。但是总是避免不了意外的发生:发生各种中断错误,或者手动打断了操作的执行
这种情况就是事务已经进行到一半了,但是不得不结束的情况。
中间过程中仍然对状态造成了影响,因此为了保持原子性,需要撤销影响,也就是回滚事务
这是站在结果角度对,只要我撤销了之前的修改,从最终结果上来看不就是完全没有做嘛,那么就是满足原子性的
比如你玩极速地平线,新手总是翻车吗,所以需要总是回溯,这个回溯的过程就差不多相当于是回滚了
对应到数据库中就是:
插入------撤销插入:删除
更新------撤销更细:更新为旧值
等等,总之就是逆操作
可以看到回滚是根据之前的修改来进行的,所以需要将之前的修改都记录下来才行
这些为了回滚而记录下来的东西称之为undo log:回滚日志
事务ID
给事务分配id的时机
事务可以是只读或者读写类型的
如果一个事务对表执行了CRU操作(增删改),那么InnoDB引擎会给他分配一个事务id:
对于只读的事务来说,只有对用户创建的临时表进行CRU操作的时候才会分配一个事务id,否则不分配id
对于读写事务来说,只有对某个表(包括用户创建的临时表)执行CRU操作的时候分配事务id,其他情况是不分配id的
总结:也就是说只有涉及到对表进行修改的操作的时候才会给事务分配事务id
事务id的生成
服务器有一个维护的系统变量来管理事务id,每次需要分配事务id的时候就将该系统变量的值分配给这个事务,并且将该变量自增1
这样可以保证事务分配是按照时间顺序得到递增的事务id
trx-id隐藏列
聚簇索引会自动给记录增加一个隐藏列:trx-id
这个隐藏列就是来存放对应的事务ID的,从名字也可以看出这个是用于管理id的
Undo日志格式
InnoDB实际操作数据的时候需要先将对应的操作记录在undo日志上,再进行操作
个人思考:这就差不多相当于一步一个脚印这样的吧,每次操作都会有相应的记录。
这里虽然叫做日志,但实际上针对的对象是一条记录而不是"一天的记录"。对应关系应该是一次改动就对应一条undo日志
所有的undo日志存储到对应的类型页中,具体就是FIL-PAGE-UNDO-LOG
INSERT操作对应的undo日志
INSERT对应的undo日志有自己的结构:
主要的如下:
undo no:记录undo日志的序号,在一个事务中它是从0开始递增的
TRX-UNDO-INSERT-REC:表示这个日志类型是一个插入类型的undo日志
<len,value>列表:记录每个列占用的存储空间大小和真实值
roll-pointer隐藏列的含义
看到这个名字就知道了,这是一个指针,其实就是对应undo日志的一个指针,具体就是指向undo存放页的一个位置
DELETE操作对应的undo日志
记录是以链表的形式存在于页中的,因此删除实际上就是对这些链表做操作:
记录头信息中有一个next-record,这个组成的是没有被删除的正常记录链表(delete-mask = 0)
被删除的记录也有一个这个字段,不过组成的是垃圾链表(delete-mask = 1)
在页的头部中有一个PAGE-FREE属性,记录着垃圾链表中的头节点
那么删除的流程具体就是操作链表的过程:
(1)delete mark:
将被删除记录的delete-mask标识为设置为1(这里是逻辑删除)
然后其他的属性不做修改------这个阶段叫做delete mark,很好理解,就是软删除
此时这个记录还存在在这个链表中,处于一种奇怪的中间状态,在删除语句所在的事务提交之前将会一直保持这种状态
(2)真正删除:purge
当删除语句的事务提交以后,就会有线程来真正删除记录:将这个记录从正常的链表中转移到垃圾链表中,然后调整页面信息:更新用户记录数量等等信息。
这个过程称之为pruge,也就是真正的清除了
整个阶段只有delete mark阶段是处于事务操作进行时的,因此针对事务的undo日志,只需要考虑阶段1进行记录即可
删除类型的undo日志结构:
主要是这几个属性需要注意:
Old trx-id 和 old roll-pointe属性------旧事务id和旧的undo日志指针
可以通过旧的指针属性将针对同一个事务的不同时期的undo日志串成一条链表:形成版本链
个人理解:这个版本链就是纵向记录一个事务不同时期的状态的随时可以回溯的一个标记,相当于提前"存档",不同时期的存档视为一个版本
<pos,len,value>索引列各列信息:如果某个列包含在某个索引中,那么这列的相关信息会记录在这里。包括该列在记录中的位置,占用的空间大小,该列的实际值
该属性主要用在阶段2,也就是提交删除事务之后使用
UPDATE操作对应的undo日志
InnoDB针对更新主键和不更新主键设置了两种情况:
(1)不更新主键的情况
如果更新的列空间发生变化:直接就地更新,在原来的基础上修改对应列的值,一个普通的更新类型的日志
如果占用的空间不一样,需要先删除旧记录再插入新值记录。这种情况对应的undo日志就是TRX-UNDO-UPD-EXIST-REC类型的undo日志
主要注意一下:
N-updated:表示本条UPDATE语句执行以后有多少条记录被更新了。后面的<pos,old-len,old-value>分别表示更新列在记录中的位置,占用存储空间的大小和更新之前的真实值
索引列各列信息:如果包含索引列,那么这个属性就会记录,否则就没有这个部分的属性内容
(2)更新主键
聚簇索引中的记录都是按照主键值进行排序的,如果更新了某条记录的主键值,那么意味着它在聚簇索引中的位置将会发生变化
这种情况稍微复杂一点:
首先将旧记录软删除,但是不直接删除,因为其他同时进行的事务也可能访问到这条记录------MVCC功能
然后根据新列的值创建新的记录,重新定位插入位置插入到聚簇索引中
针对主键值更新的情况会记录一条类型为:TRX-UNDO-DEL-MARK-REC的undo日志,插入的时候也会记录一条INSERT类型的undo日志,也就是每次对一条记录的主键值做修改的时候需要写两条undo日志
总结:
Redo日志是记录需要做的事,而undo日志是记录撤销修改的时候需要的东西:
比如撤销插入需要记录之前的主键值,撤销修改需要记录之前的旧值等等···
本章主要介绍的是undo日志的原理以及用途