undo log定义
- 由于事务是要保证原子性的,但是有些情况会造成事务异常或回滚,所以数据库为了解决事务一致性问题而记录的日志,我们就称之为undo log(撤销日志)。
- 由于SELECT操作并不会修改任何记录,所以并不需要记录相应的undo日志。
事务id
- 定义:标记事务唯一性。存储在
- 分配事务id的时间
- 只读的事务:创建临时表,如果对临时表进行了增删改操作,才会分配一个事务id(比较少)
- 读写的事务:一般都是进行读写的事务,只有一个事务第一次对表中的数据进行增删改操作的时候,才会分配一个事务id。
- 事务id默认是0
- 如何开启事务
- 针对只读的事务:START TRANSACTION READ ONLY
- 针对读写的事务:START TRANSACTION READ WRITE 、BEGIN、START TRANSACTION
- 生成和维护事务id
- 事务id就是一个数字
- 与row_id类似,都是有一个全局变量,当需要使用事务id的时候,就会从赋值给事务id,然后自增1,当增长到256的倍数时,将这个值存储到表空间中页号为5的MAX_TRX_ID字段中(参考另一篇文章redo日志中的redo简单日志类型的使用场景。
insert操作对应的undo日志
- 主要是TRX_UNDO_INSERT_REC类型的undo日志结构,如下图所示:
end of record:本条undo日志结束的页面地址
undo type:保存对应的undo日志类型,这里存储的就是"TRX_UNDO_INSERT_REC"
undo no:undo日志记录的编号
table id:表的唯一标识
主键各列信息<len,value>列表:记录的是主键的信息,存储的是主键对应的长度及主键的值,因为进行数据undo的时候,只需要按照主键的聚簇索引进行删除,二级索引会自动根据聚簇索引操作。
start of record: 本条undo日志开始的页面地址
- 示例:插入两条数据
首先使用BEGIN显式开启一个事务,假设此次的trx_id为100,然后insert into tb_student(id,name,city)values (1,‘muse’,'北京市'),(2,‘bob’,'上海市');其中id为int类型,剩下的为varchar2,此时存储的undo日志如下所示:
0是undo no 写入undolog之后从0开始递增;<4,1>前面的4是int占4个字节,1是主键值;此时的tb_student的table_id为1065,查询表的table_id的方法为:
select * from information_schema.innodb_sys_tables;-- mysql5
select * from information_schema.innodb_tables; -- mysql8
delete操作对应的undo日志
- 主要是TRX_UNDO_DEL_MARK_REC类型的undo日志结构,如下图所示:
相同名称的字段与insert中含义完全一致,不再赘述。
info_bits:记录了头结点的前四个比特值,作用不大
trx_id:被删除的记录的trx_id
roll_pointor:也是被删除的记录的值,指向垃圾链表的头结点(垃圾链表:page中正常的单向链表及B+树的双向叶子节点等记录正常业务数据的链表都是正常记录链表,垃圾链表就是为了保存已经被删除的数据,page是有多种类型的,undo也是其中的一种类型,undo页,可以通过一个页中的next record找到下一个页,被删除的记录通过next record连成一个对应的链表就是垃圾链表)
主键各列信息<len,value>列表:记录的是主键的信息,存储的是主键对应的长度及主键的值,因为进行数据undo的时候,只需要按照主键的聚簇索引进行删除,二级索引会自动根据聚簇索引操作。
len of index_col_info:存储的是索引列各列的信息长度之和加上len of index_col_info本身的长度
索引列各列信息<pos,len,value>列表: pos:表中每一列对应在记录中的位置
- 删除记录分为两个阶段:
- delete mark:删除标记,将deleted_flag置为1
上图左为正常记录,page_free是page header中的一个值,指向垃圾链表的头结点,上图右为已经进行了delete mark阶段的数据状态,可以看出只是进行了deleted_flag的置位,还没有移动到垃圾链表中
- purge
此阶段将被删除的记录从正常记录中断开,page_free指向刚刚被删除的记录,所以这条记录就成为了垃圾链表的头节点,同时将该记录指向垃圾链表的原头结点。
- 示例:删除一个记录 delete from tb_student where id =1;(删除上面insert的那条id为1的记录)
2:接着上面的insert之后,递增,所以值为2
100:insert id为1的记录时的trx_id
roll_pointer:指向insert那条undo日志的结束地址
<0,4,1>:0是在记录中的位置,因为id是主键,所以位置是0,4是int类型占用长度,1是值
<3,4,'muse'>:在tb_student表上,除了id建了主键索引之外,name这一列也建了二级索引;3是name这一列在记录中的位置,4是'muse'所占大小,'muse'是对应的值
14:根据<0,4,1>以及<3,4,'muse'>以及其本身大小2计算:pos记录位置1字节,len记录数值长度,1字节,int类型4字节,'muse'占用4个字节,
所以<0,4,1>=1+1+4 = 6 ;<3,4,'muse'> = 1+1+4 = 6 ;总大小为:6+6+2=14
update操作对应的undo日志
主要是TRX_UNDO_UPD_EXIST_REC类型的undo日志,结构如下图所示:
n_updated:记录update语句执行之后被更新的列的个数
被更新的列更新前的信息<pos,old len,old value>列表:记录了修改之前的信息
更新包括以下两种情况:
- 不更新主键:
- 当所有的new value的长度与old value 的长度完全一致时,不用额外申请新的空间,直接在原记录上进行修改,被称为复用。
- new value的长度与old value 的长度不一致时,如果新的数据长度小于旧长度,此时也会复用原空间,只不过会产生空间碎片;当新数据长度大时,会删除记录并向页申请新的空间,如果页空间也不足,则进行页分裂及后续操作
- 更新主键
主要是下面两步:先将原纪录删除,然后将新的记录作为一条全新的记录插入页中,同步维护聚簇索引,二级索引
**************此文章只是本人学习过程中的学习笔记,不做其他用途,如果有其他意见,欢迎一起讨论,谢谢,侵删*************************