undo日志
前面学习了redo日志,redo日志保证的是崩溃时事务持久性。我们可以从redo日志恢复到系统崩溃以前。
undo日志就是为了保证事务回滚时事务所作所为都能回到事务执行前。保证了事务的原子性。redo把我们做增删改之前的状态记录下来,帮助MySQL回滚到事务执行之前的样子。
这篇文章了解一下事务ID和undo日志产生就OK了,对于Undo日志存储可以直接略过。
事务ID
事务两种类型:只读事务,读写事务。
针对于只读事务,MySQL会在其对用户创建的临时表进行增删改的时候才会为其分配事务ID,否则不分配。
这里的临时表指的是create temporary 表名,和我们使用explain SQL的时候在extra上显示的using temporary不一样。前者是用户创建的用户临时表,只针对于当前session有效,后者是MySQL内部临时表。
而针对于读写事务来说,MySQL会在事务执行对某个表进行增删改的时候为其分配一个事务ID,否则不分配。
事务ID生成
在系统启动时,系统维护一个全局变量,我们首先从内存中找到MAX TRX ID这个值然后加上256,赋给这个全局变量。
每次出现上述情况的事务时会为其分配一个ID,然后变量进行+1操作。
然后每每这个全局变量是256的倍数时候就会对这个变量进行同步修改到系统表空间中的MAX TRX ID属性处。
然后我们为什么在取值的时候要加上256呢?主要是因为我们系统关闭是可能已经大于当前MAX TRX ID但是还没有到256的倍数,所以我们只要将其+256,就会得到一个唯一的事务ID咯。
trx_id隐藏列
我们在介绍数据行的时候就已经提到数据行的三个隐藏列
- row_id 就当我们没有主键或者unique列的时候,会生成一个这个唯一的row_id来保持记录的唯一性
- trx_id 事务ID
- roll_pointer 后面MVCC的时候介绍,这里链着一个版本链🤭
undo日志类型
为了保证原子性,所以Innodb对每个增删改都会在改之间进行一次undo日志的记录。
下面就来介绍一下增、删、改也就是insert、delete、update会产生的undo日志类型以及其细节吧。
INSERT的undo日志
我们使用insert的SQL语句时,就会产生一个TRX_UNDO_INSERT_REC类型的undo日志。
- end of record 和 start of record就是指向尾部和指向头部的地址。
- undo type 就是TRX_UNDO_INSERT_REC
- undo no 就是undo日志的no,对于每个事务来说这个值会从0开始,每个事务维护一个no从0开始慢慢+1递增。
- table id就是这个日志所在的表id
- 主键信息,多个列就是又多个<len,value>。len是主键占用空间的大小,value就是它的值。
所以innodb回滚会发生什么?首先呢,我们插入可能是悲观插入和乐观插入,悲观就是页满了得进行分裂,乐观就是没满直接将数据行插入。但是呢?innodb做的还是得到插入的主键值,然后删除主键值对应的聚簇索引和二级索引。
当我们执行insert插入时,插入的数据行中的隐藏列,我们关心的主要是事务ID和roll_pointer这两个属性。
- 因为我们执行的是插入,所以执行语句的事务会生成一个唯一的事务ID。后面我们将插入,修改,删除都会有一个唯一的事务ID,就不必多讲了。
- roll_pointer指向的就是innodb生成的undo日志。
上图我们在一个事务中先后执行了2次插入操作,它的roll_pointer都指向了对应生成的undo日志。后续将介绍存储undo的页面。
DELETE的undo日志
delete操作就稍微有点特殊了。
我们在前面介绍页面的时候介绍了一个数据行的next_record属性,就是指向下一个数据行的指针。我们也说过这些被删除的数据行是可以被重用的,它其实是被一个存储在PAGE_HEADER中的一个PAGE_FREE的属性给链起来了,也就是说被删除的垃圾页面会形成一个链表。