目录
前言
回答
1. 从redolog的角度(从这一点答出来,应该会让面试官很惊讶吧哈哈)
2.从undolog的角度
事务id是怎么生成的
roll_pointer隐藏列的含义
delete操作对应的redolog
加入到链表的头节点处与PAGE_GARBAGE
update对应的undolog
不更新主键的情况
更新主键的情况
版本链
总结
内容摘抄
面试被问到了吗,感觉还蛮重要的。
没看看redolog和undolog的实现,还真不敢说这个原子性是怎么实现的。
看完觉得主要分为两方面吧。
主要参考文章:https://juejin.cn/book/6844733769996304392/section/6844733770067607566
[mysql]innodb的redolog
规定在执行这些需要保证原子性的操作时必须以组的形式来记录的redo日志,在进行系统崩溃重启恢复时,针对某个组中的redo日志,要么把全部的日志都恢复掉,要么一条也不恢复。
上一篇笔记的时候,就觉得这个点也算是原子性很重要的一点。
即:以组的形式来进行redo log的恢复,要么都成功,要么都失败!
首先只有在事务对表中的记录做改动时才会为这个事务分配一个唯一的事务id;
服务器会在内存中维护一个全局变量,每当需要为某个事务分配一个事务id
时,就会把该变量的值当作事务id
分配给该事务,并且把该变量自增1。
还有一些巴勒巴拉的操作其实也无所谓记住
为了实现事务的原子性
,InnoDB
存储引擎在实际进行增、删、改一条记录时,都需要先把对应的undo日志
记下来。一般每对一条记录做一次改动,就对应着一条undo日志
,但在某些更新记录的操作中,也可能会对应着2条undo日志:(
每对一条记录的主键值做改动时,会记录2条undo日志,因为会有
对该记录进行delete mark
操作前,会记录一条类型为TRX_UNDO_DEL_MARK_REC
的undo日志
;之后插入新记录时,会记录一条类型为TRX_UNDO_INSERT_REC
的undo日志)
undo日志的格式
1.一次事务如果有很多undolog,会进行编号,比如undolog 1, 2, 3 就是undo no
2.undo日志
是被记录到类型为FIL_PAGE_UNDO_LOG
的页面中(专门给undo log 弄一个页面)
3.每个表都会被分配一个唯一的table id
4.
INSERT操作对应的undo日志
其实对于undo日志结构,就是有地址,然后列的真实值,然后还有对应的记录表的tableid,然后undo no,还有是什么类型,next 记录的地址,都很好理解。
roll_pointer
的真实面纱了,指向记录对应的undo日志
的一个指针。
(其实我觉得这个跟页面的结构串起来了,可以给面试官讲讲)
从图中也可以更直观的看出来,roll_pointer
本质就是一个指针,指向记录对应的undo日志。
正常情况:插入到页面中的记录会根据记录头信息中的next_record
属性组成一个单向链表
被删除的记录其实也会根据记录头信息中的next_record
属性组成一个链表,只不过这个链表中的记录占用的存储空间可以被重新利用,所以也称这个链表为垃圾链表
。
(注意这个垃圾链表并不是独立出来的吗,而是,就是利用原本的空间,然后更改指针位置)
我觉得可以认为就是可以从比如pagefree的节点,依次往下找到被删除的记录。
删除操作对应:(因为会有事务)
1. 仅仅将记录的delete_mask
标识位设置为1
,其他的不做修改(其实会修改记录的trx_id
、roll_pointer
这些隐藏列的值)。delete mark
。
挺有意思的,这是一个中间状态:
2.当该删除语句所在的事务提交之后,会有专门的线程后来真正的把记录删除掉。所谓真正的删除就是把该记录从正常记录链表
中移除,并且加入到垃圾链表
中,然后还要调整一些页面的其他信息,比如页面中的用户记录数量PAGE_N_RECS
、上次插入记录的位置PAGE_LAST_INSERT
、垃圾链表头节点的指针PAGE_FREE
、页面中可重用的字节数量PAGE_GARBAGE
、还有页目录的一些信息等等。这个阶段叫purge
。
(将被删除记录加入到垃圾链表
时,实际上加入到链表的头节点处,会跟着修改PAGE_FREE
属性的值。)
为啥加入到头结点处?看下面
Page Header部分有一个PAGE_GARBAGE属性,该属性记录着当前页面中可重用存储空间占用的总字节数。每当有已删除记录被加入到垃圾链表后,都会把这个PAGE_GARBAGE属性的值加上该已删除记录占用的存储空间大小。
PAGE_FREE指向垃圾链表的头节点,之后每当新插入记录时,首先判断PAGE_FREE指向的头节点代表的已删除记录占用的存储空间是否足够容纳这条新插入的记录,如果不可以容纳,就直接向页面中申请新的空间来存储这条记录(是的,你没看错,并不会尝试遍历整个垃圾链表,找到一个可以容纳新记录的节点)。如果可以容纳,那么直接重用这条已删除记录的存储空间,并且把PAGE_FREE指向垃圾链表中的下一条已删除记录。
(其实就是判断头后面的被删除的记录可不可以被重用)
PAGE_GARBAGE这个东西到底是啥呢?
其实就是:如果新插入的那条记录占用的存储空间大小小于垃圾链表的头节点占用的存储空间大小,那就意味头节点对应的记录占用的存储空间里有一部分空间用不到,这部分空间就被称之为碎片空间。那这些碎片空间岂不是永远都用不到了么?其实也不是,这些碎片空间占用的存储空间大小会被统计到PAGE_GARBAGE属性中,这些碎片空间在整个页面快使用完前并不会被重新利用,不过当页面快满时,如果再插入一条记录,此时页面中并不能分配一条完整记录的空间,这时候会首先看一看PAGE_GARBAGE的空间和剩余可利用的空间加起来是不是可以容纳下这条记录,如果可以的话,InnoDB会尝试重新组织页内的记录,重新组织的过程就是先开辟一个临时页面,把页面内的记录依次插入一遍,因为依次插入时并不会产生碎片,之后再把临时页面的内容复制到本页面,这样就可以把那些碎片空间都解放出来(很显然重新组织页面内的记录比较耗费性能)。
(这样也太节省资源了吧,这个过程真的很有趣)
TRX_UNDO_DEL_MARK_REC的结构:
大体看一下就行:
都差不多的玩法。
在执行UPDATE
语句时,InnoDB
对更新主键和不更新主键这两种情况有截然不同的处理方案。
在不更新主键的情况下,又可以细分为被更新的列占用的存储空间不发生变化和发生变化的情况。
就地更新(in-place update)(直接在原记录的基础上修改对应列的值,任何值的存储空间都不变)
先删除掉旧记录,再插入新记录。 (在不更新主键的情况下,如果有任何一个被更新的列更新前和更新后占用的存储空间大小不一致,那么就需要先把这条旧的记录从聚簇索引页面中删除掉,然后再根据更新后列的值创建一条新的记录插入到页面中。)
删除
是真正的删除掉,也就是把这条记录从正常记录链表
中移除并加入到垃圾链表
中,并且修改页面中相应的统计信息(比如PAGE_FREE
、PAGE_GARBAGE
等这些信息)。用户线程同步执行真正的删除操作,真正删除之后紧接着就要根据各个列更新后的值创建的新记录插入。
(我这边有个问题,那岂不是,频繁的update,最后总要有重新迁移表的过程?,其实就是页分裂,进行插入操作)
这里如果新创建的记录占用的存储空间大小不超过旧记录占用的空间,那么可以直接重用被加入到垃圾链表
中的旧记录所占用的存储空间,否则的话需要在页面中新申请一段空间以供新记录使用,如果本页面内已经没有可用的空间的话,那就需要进行页面分裂操作,然后再插入新记录。
在聚簇索引中,记录是按照主键值的大小连成了一个单向链表的,如果我们更新了某条记录的主键值,意味着这条记录在聚簇索引中的位置将会发生改变,比如你将记录的主键值从1更新为10000,如果还有非常多的记录的主键值分布在1 ~ 10000
之间的话,那么这两条记录在聚簇索引中就有可能离得非常远,甚至中间隔了好多个页面。针对UPDATE
语句中更新了记录主键值的这种情况,InnoDB
在聚簇索引中分了两步处理:
将旧记录进行delete mark
操作(初步标记)
UPDATE
语句所在的事务提交前,对旧记录只做一个delete mark
操作,在事务提交后才由专门的线程做purge操作,把它加入到垃圾链表中。这里一定要和上边所说的在不更新记录主键值时,先真正删除旧记录,再插入新记录的方式区分开!
小贴士: 之所以只对旧记录做delete mark操作,是因为别的事务同时也可能访问这条记录,如果把它真正的删除加入到垃圾链表后,别的事务就访问不到了。这个功能就是所谓的MVCC,我们后边的章节中会详细唠叨什么是个MVCC。
握草,我的mvcc又精进了一步
根据更新后各列的值创建一条新记录,并将其插入到聚簇索引中(需重新定位插入的位置)。
由于更新后的记录主键值发生了改变,所以需要重新从聚簇索引中定位这条记录所在的位置,然后把它插进去。
针对UPDATE
语句更新记录主键值的这种情况,在对该记录进行delete mark
操作前,会记录一条类型为TRX_UNDO_DEL_MARK_REC
的undo日志
;之后插入新记录时,会记录一条类型为TRX_UNDO_INSERT_REC
的undo日志
,也就是说每对一条记录的主键值做改动时,会记录2条undo日志
。
执行完delete mark
操作后,它对应的undo
日志和INSERT
操作对应的undo
日志就串成了一个链表。这个很有意思啊,这个链表就称之为版本链
。
比如删除会把删除操作和insert操作串起来,组成版本链。
1. 之所以只对旧记录做delete mark操作,是因为别的事务同时也可能访问这条记录,如果把它真正的删除加入到垃圾链表后,别的事务就访问不到了。这个功能就是所谓的MVCC,我们后边的章节中会详细唠叨什么是个MVCC。
这点很重要,丰富mvcc的体系!
来自:
https://juejin.cn/book/6844733769996304392/section/6844733770067607566
作者也写书了,有机会一定支持一下!