InnoDB之Undo log格式
1. 前言
InnoDB有两大日志模块,分别是redo log和undo log。为了避免磁盘随机写,InnoDB设计了redo log,数据写入时只写缓冲页和redo log,脏页由后台线程异步刷盘,哪怕系统崩溃也能根据redo log恢复数据。但是我们漏了一种情况没有考虑,如果事务执行到一半系统崩溃了,redo log没刷盘还好,相当于本次事务的修改全部停留在内存里,重启后相当于什么也没做。但是,如果redo log已经刷盘了,MySQL重启后依然会根据redo log恢复页面,相当于本次事务执行到一半的状态,不符合原子性。为了保证原子性,MySQL必须撤销本次事务的所有修改,让本次事务「看起来什么都没做」,这就是undo log要负责的事情。
2. 事务回滚
事务回滚的需求是存在的,除了上述情况系统崩溃时的执行了一半的事务需要回滚,很多时候,开发者也经常需要通过命令ROLLBACK
手动回滚事务。事务回滚后,该事务看起来什么都没做一样,它是符合原子性的。
如何实现事务回滚呢?想当然,肯定要把事务中修改的数据先记下来,比如:
- insert一条记录,就把主键记下来,回滚时删除该记录即可。
- delete一条记录时,把整条记录记下来,回滚时重新插入即可。
- update一条记录时,把对应列修改前的值全部记下来,回滚时修改回来即可。
- select不会修改记录,无需处理。
InnoDB其实也就是按照这个思路去设计的,每次对记录的修改,都会记一条日志,把回滚该条记录的必要数据给记录下来,这个日志就是undo log。
3. undo log格式
undo log是针对记录的,一般每对一条记录进行一次改动,都会生成1到2条undo log。一个事务在执行过程中,可能会修改很多记录,也就会生成若干条undo log,每个事务生成的undo log都会有一个唯一编号undo no
,从0开始依次递增,undo no越小代表日志越早生成。
另外,undo log只针对聚簇索引,只有聚簇索引记录才有trx_id
和roll_pointer
隐藏列,二级索引是不会生成undo log的,MySQL在事务回滚时,会自动撤销对二级索引的变更。
roll_pointer
隐藏列占用7个字节,组成如下:
属性 | 长度 | 说明 |
---|---|---|
is_insert | 1比特 | 是否是TRX_UNDO_INSERT大类 |
rseg id | 7比特 | 回滚段id,最多128个回滚段 |
Page Number | 4字节 | undo log所在页号 |
Offset | 2字节 | undo log所在页号的偏移量 |
和redo log一样,InnoDB也设计了很多不同类型的undo log,增删改操作对应的undo log类型都不一样。
3.1 insert undo log
插入一条记录,对应的回滚操作就是删除该条记录,对应的undo log最需要记录的就是tableId和主键信息。InnoDB设计了TRX_UNDO_INSERT_REC
类型的undo log来回滚insert操作。
属性 | 说明 |
---|---|
end of record | 本条undo log结束,下一条开始的位置 |
undo type | undo log类型 |
undo no | undo log序号 |
table id | 表对应的id |
主键信息 | |
<len,value>列表 | 主键各列的长度以及对应的值 |
start of record | 上一条undo log结束,本条开始的位置 |
重点关注主键信息,假设表的主键是BIGINT类型的id,我们插入了一条id=10000的记录,那么主键信息存储的内容就是<8,10000>
,如果主键包含多列,需要把每个列的长度和值都记录下来。
3.2 delete undo log
删除一条记录,对应的回滚操作就是把这条记录再重新插入回去,难道undo log要把一条用户记录完整的给记录下来吗?这未免也太浪费空间了,其实完全不需要这么做,这还得说回InnoDB删除记录的流程。
记录头信息里会有next_record
属性,把记录按照主键串联成一条单向链表。页内被删除的记录也会根据该属性串联成一条单向链表,只不过这条链表的空间是可以被重用的,也称作「垃圾链表」。索引页Page Header里有PAGE_FREE
属性,指向这条垃圾链表的头节点。记录头信息里还有delete_mark
属性,用来标记记录是否被删除。
当我们要删除一条记录时,实际上会有两个阶段:
- 阶段1
将记录的delete_mark
标记为1,记录undo log,写入trx_id
和roll_pointer
。事务提交前,记录一直处于这种中间状态,既不是正常记录,也不是已删除记录。只有将记录从正常链表中移除,加入到垃圾链表里,记录才算真正删除,其它事务也访问不到了。
为啥不直接删除记录,而是停留在中间状态?
这条记录还需要为MVCC服务,其它事务可能还需要访问。
- 阶段2
事务提交后,会有专门的线程来将记录真正的删除掉,这个过程称作「purge」。将记录从正常链表中移除,加入到垃圾链表,InnoDB采用头插法,PAGE_FREE
会指向该记录,记录占用的空间也可以被重用了。与此同时,InnoDB还会修改Page Header里的PAGE_N_RECS
、PAGE_GARBAGE
、Page Directory等信息。
综上所述,事务提交前,只会经历阶段1,事务提交后也就不存在回滚了。所以针对delete操作,只需要把阶段1回滚即可,又因为阶段1记录其实并没有真正删除,所以undo log其实没必要保存完整记录。InnoDB设计了TRX_UNDO_DEL_MARK_REC
类型的undo log。
属性 | 说明 |
---|---|
end of record | 本条undo log结束,下一条开始的位置 |
undo type | undo log类型 |
undo no | undo log序号 |
table id | 表对应的id |
info bits | 记录头信息的前4个比特位和record_type值 |
old trx_id | 旧的事务id |
old roll_pointer | 旧的回滚指针 |
主键信息 | |
<len,value>列表 | 主键各列长度和值 |
index_col_info len | 索引列信息总长度 |
索引各列信息 | |
<pos,len,value> | 索引各列的位置、长度和值 |
start of record | 上一条undo log结束,本条开始的位置 |
- 与insert不同的是,delete和update操作对应的undo log会记录下旧的
trx_id
和roll_pointer
,这样就可以找到上一次对记录修改时的undo log,这些undo log串联起来就是传说中的「版本链」,服务于MVCC。 - 根据主键信息定位到具体的记录,用户回滚时恢复。
- 索引各列信息主要用于purge阶段。
3.3 update undo log
update操作就比较复杂了,根据是否更新主键,InnoDB的处理方式也是不同的。
一、不更新主键
在不更新主键的前提下,如果更新后记录各列的长度与更新前相同,那么就可以「就地更新」,也就是直接在原有记录上进行更新,同时记录下undo log。
注意:是每个列的长度都和更新前相同,而非记录总长度和更新前相同。
就地更新的条件还是比较苛刻的,如果更新后列的长度发生变化,那么InnoDB会采用「先删除旧记录,再插入新记录」的方式来做更新,这里的“删除”是真的将记录删除并移入垃圾链表,而非仅仅打删除标记。
为什么会这么做呢?
在索引页里记录与记录之间是紧密无间的存储在一起的,中间没有空间,如果更新后记录占用的空间变大压根就没法存储,只能删掉重新申请空间插入一条。
总之,针对这种不更新主键的情况,InnoDB设计了TRX_UNDO_UPD_EXIST_REC
类型的undo log。
属性 | 说明 |
---|---|
end of record | 本条undo log结束,下一条开始的位置 |
undo type | undo log类型 |
undo no | undo log序号 |
table id | 表对应的id |
info bits | 记录头信息的前4个比特位和record_type值 |
old trx_id | 旧的事务id |
old roll_pointer | 旧的回滚指针 |
主键信息 | |
<len,value>列表 | 主键各列长度和值 |
n_updated | 更新的列的数量 |
<pod,ole_len,old_val>列表 | 更新列的旧值 |
index_col_info len | 索引列信息总长度 |
索引各列信息 | |
<pos,len,value> | 索引各列的位置、长度和值 |
start of record | 上一条undo log结束,本条开始的位置 |
二、更新主键
针对update操作更新了主键的情况,InnoDB分为两个阶段来处理:
- 将旧记录进行delete mark操作,服务于MVCC。
- 根据更新后各列的值构建一条新记录并插入。
这两个阶段,对应两条undo log,也就是上面说的TRX_UNDO_DEL_MARK_REC
和TRX_UNDO_INSERT_REC
。
4. 对覆盖索引查询的影响
聚簇索引记录会有trx_id
和roll_pointer
隐藏列,通过undo log里的roll_pointer
串联形成版本链,即一条记录存在多个版本,在select时会判断哪些版本对当前事务可见。
但是undo log只针对聚簇索引,二级索引没有roll_pointer
,也不会生成undo log。我们又知道,InnoDB有个查询优化叫「覆盖索引查询」,即直接扫描二级索引返回结果,不再根据主键回表查询,可以大大提高数据查询的效率。
这时就存在一个问题,覆盖索引查询时,无法判断二级索引记录是否对当前事务可见!
InnoDB的解决方案是,在Page Header里有一个属性叫PAGE_MAX_TRX_ID
,它代表修改当前页的最大事务id,如果PAGE_MAX_TRX_ID
小于当前活跃的最小事务id,代表修改当前页的事务都提交了,可以直接使用覆盖索引查询,无需回表。反之,就需要回表根据聚簇索引的trx_id
和roll_pointer
以及对应的undo log来判断哪些二级索引记录是对当前事务可见的。
InnoDB之Undo log格式相关推荐
- 庖丁解InnoDB之UNDO LOG
简介: Undo Log是InnoDB十分重要的组成部分,它的作用横贯InnoDB中两个最主要的部分,并发控制(Concurrency Control)和故障恢复(Crash Recovery),In ...
- 庖丁解 InnoDB 之 UNDO LOG
Undo Log是InnoDB十分重要的组成部分,它的作用横贯InnoDB中两个最主要的部分,并发控制(Concurrency Control)和故障恢复(Crash Recovery),InnoDB ...
- InnoDB文档笔记(三)—— Undo Log
接上篇,官网地址 一.简介 Undo Log是一组因读写事务产生的Undo log records 的集合,是数据的修改记录,是逻辑日志. Undo Logs由undo log segments组成, ...
- Innodb中的buffer poll和redo undo log
内存缓冲池 buffer pool,如果mysql不使用内存缓冲池,每次读取数据时,都需要访问磁盘,会大大的增加磁盘的IO请求,导致效率低下:在Innodb引擎在读取数据的时候,把相应的数据和索引载入 ...
- 说说MySQL中的Redo log Undo log都在干啥
在数据库系统中,既有存放数据的文件,也有存放日志的文件.日志在内存中也是有缓存Log buffer,也有磁盘文件log file,本文主要描述存放日志的文件. MySQL中的日志文件,有这么两类常常讨 ...
- 1009MySQL数据库InnoDB存储引擎Log漫游
00 – Undo Log Undo Log 是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中,还用Undo Log来实现多版本并发控制(简称:MVCC). - 事务的原子性(Atom ...
- Undo Log学习
一 Undo Log的作用 ** ** 数据库故障恢复机制的前世今生中提到过,Undo Log用来记录每次修改之前的历史值,配合Redo Log用于故障恢复.这也就是InnoDB中Undo Log的第 ...
- MySQL内核月报 2014.11-MySQL· 5.7特性·在线Truncate undo log 表空间
背景 Innodb使用undo log来实现MVCC,这意味着如果一个很老的事务长时间不提交,那么新产生的undo log都无法被及时清理掉.在MySQL 5.5及之前版本中,undo log是存储在 ...
- MySQL深度剖析之undo log redo log binlog专题(2021)
因为每次对磁盘随机读写影响性能,尤其是高并发的时候,所以引入了Buffer Pool, 即只要更新Buffer Pool中的记录,则算更新成功,那如果更新完了还没有flush到磁盘则宕机了,此时内存的 ...
最新文章
- 由一个园友因为上传漏洞导致网站被攻破而得到的教训
- 2021年春季学期-信号与系统-第十四次作业参考答案-第三小题参考答案
- Javascript 获取字符串字节数的多种方法
- [译] ASP.NET 生命周期 – ASP.NET 上下文对象(八)
- chrome扩展程序获取当前页面URL和HTML内容
- OpenShift 4 之 GitOps(4)用ArgoCD向Multi-Cluster发布应用
- 服务器修改文件句柄数,请问如何修改文件最大句柄数?
- Poi实现Excel的导入
- zynq开发系列4:EMIO连接按键控制MIO连接的灯
- MacOs中Docker与宿主机网络互通问题解决
- Asia Hong Kong Regional Contest 2016
- Maven引入数据库JDBC驱动
- 解决游戏程序被恶意“游戏启动器”替代的问题
- 【OD矩阵】《城市公交IC卡·数据分析方法及应用》利用公交运营时间和乘客刷卡时间特征识别上车点
- 一个C/C++协程库的思考与实现之协程栈的动态按需增长
- Python | 人脸识别系统 — 用户操作
- CTF pyc之stegosaurus隐写
- Opencv(C++)笔记--模板匹配cv::matchTemplate()和最值计算cv::minMaxLoc()
- grads中画站点图
- 全新 ADAS 和自动驾驶车辆系统的处理器性能和安全要求