什么是MVCC
在学习MVCC前,先了解一下当前读和快照读
- 当前读
比如lock in share mode(共享锁),for update,update,delete,insert(排它锁)这些操作都是一些当前读,当前读的定义就是读取当前数据的最新版本,读取时还要保证其它并发事务不能修改数据,会对读取的记录进行加锁
- 快照读
不加锁的select就是快照读,即不加锁的非阻塞读,快照读的前提是隔离级别不是串行级别,串行级别下快照读会退化为当前读。快照读的实现是基于多版本并发控制,即MVCC。即快照读可能读取到的数据不一定是最新版本,而有可能是之前的历史版本
什么是UndoLog 回滚日志(撤销日志),MVCC的重要组成部分,单个事务对记录变更操作的记录集合。如果另一个事务需要查看原始数据满足一致性读,则需要去undolog中检索,UndoLog保存在UndoLog Segments中,UndoLog Segments 保存在Rollback Segments中,回滚段保存在Undo表空间和全局临时表空间中。UndoLog一般是逻辑日志,记录数据的变更版本信息
mvcc实现原理 基于隐式字段+UndoLog+Read View实现
UndoLog中的每条记录都有隐藏字段
- DB_TRX_ID 创建该条记录或者最后修改该条记录的事务ID
- DB_ROLL_PTR 回滚指针,指向当前这条记录的上一个版本
- DB_ROW_ID 隐藏的主键
Read View 可读视图 组成 [未提交事务数组],最大的事务id
可读视图判断规则 生成一致性视图规则read-view
由执行时所有未提交事务的id数组(数组里最小的trx_id为min_id)和已创建的事务id(max_id)组成,查询的数据结果根据生成 的read-view做对比得到快照的结果
文章图片
版本链比对判断规则
- 如果落在绿色部分(trx_id
- 如果落在黄色部分(min_id<=trx_id<=max_id),分两种情况处理
a. 若row的trx_id在数组中,表示这个事务是由还没提交的事务生成的,不可见,当前自己的事务是可见的
b. 若row的trx_id不在数组中,表示这个事务是已经提交的事务生成的,可见
- 如果落在红色部分(trx_id>max_id), 表示这个版本是由将来启动的事务创建的 ,肯定不可见
- 如果落在黄色部分(min_id<=trx_id<=max_id),分两种情况处理
举例说明MVCC实现原理 表说明
- t 事务操作表,演示表,undolog日志演示操作表
- t1 ,辅助表,生成事务id
验证
- 查看数据库隔离级别(只有可重复读和读提交下MVCC有效)
在RR级别下的某个事务对某条记录的第一次快照读会创建一个快照及read-view,将当前系统活跃的其它事务记录起来,此后在调用快照读的时候还是使用的同一个read-view,所以只要当前事务在其它事务更新提交之前使用快照读,那么之后的快照读都是统一个read-view,所以对之后的修改不可见。
在RR级别下,快照读生成read-view时,read-view会记录此时所有其它事务的快照,这些事务对与当前事务都是不可见的,而早于read-view创建的事务都是可见的
而在RC级别下,事务中每次都会生成一个新的read-view和快照,这就是我们在RC级别下可以看到别的事务提交的更新的原因
总之就是在RC级别下,每个事务都会获取最新的read-view;而在RR级别下,则是同一个事物的第一个selete才会创建快照,之后的快照读都是第一次创建的read-view,都是同一个read-view
- 下面为不同时刻不同事务时序图
transaction100,200,300 为三个更新事务
select1,2为两个读取事务
Transaction 100 Transaction 200 Transaction 300 select 1 select 2 begin; begin; begin; begin; begin; update t1 set t1col='t1colval' where id = '1'; (辅助表t1中更新数据,生成一个事务ID100) update t1 set t1col='t1colval200' where id = '2'; (辅助表t1生成事务ID200) update t set name = 'zuiyu300' where id = '1' ; (第一次更新数据) commit; select name from t where id = '1' ; (第一次读,创建readview[100,200] 300) 此时读出来的结果是name=zuiyu300 update t set name='zuiyu100' where id = '1'; Update t set name ='zuiyu101' where id = '1'; select name from t where id = '1'; (第二次读,继续使用第一次创建的readview[100,200] 300) 此时读出来的结果是name=zuiyu300 commit; update t set name='zuiyu200' where id = '1'; update t set name='zuiyu201' where id = '1'; select name from t where id = '1'; (第三次读,继续使用第一次创建的readview[100,200] 300) 此时读出来的结果是name=zuiyu300 select name from t where id = '1'; (此时会生成一个readview [200],300),此时结果name=zuiyu101 commit; commit; commit;
- 第一次读取时
生成的read view [100,200]300,min_id=100,max_id=300
【MySQL-MVCC全网最详细解读】版本链信息如下:
文章图片
查找规则为
1、第一条记录trx_id=300,落在中间区域
2、300不在获取事务数组中,所以可见,返回name=zuiyu300
- 第二次读取时
当前隔离级别为可重复度,使用第一次读取时生成的read view,生成的read view [100,200]300,min_id=100,max_id=300,
版本链信息如下
文章图片
查找规则
1、第一条记录trx_id=100,落在中间黄色区域
2、事务id100在活跃事务数组中,表明不可见,继续往下找
3、第二条记录trx_id=100,同比不可见
4、第三条记录trx_id=300,落在中间区域,不在活跃事务数组,可见,返回name=zuiyu300
- 第三次读取时
当前隔离级别为可重复度,使用第一次读取时生成的read view,生成的read view [100,200]300,min_id=100,max_id=300
版本链信息如下
文章图片
查找规则
1、第一条记录trx_id=200,落在中间黄色区域,200在活跃事务数组中,不可见
2、第二条记录trx_id=200,不可见
3、第三条记录trx_id=100,落在中间黄色区域,100在活跃事务数组中,不可见
4、第四条记录trx_id=100,不可见
5、第五条记录trx_id=300,落在中间黄色区域,不在活跃事务数组中,可见返回name=zuiyu300
- select 2 事务读取时
生成的read view [200],300,min_id=200,max_id=300
此时的活跃事务只有事务id为200的,当前的版本链信息如下:
查找规则为
1、从上往下,第一条记录trx_id=200,在活跃事务数组中,不可见
2、第二条记录trx_id=200,不可见
3、第三条记录trx_id=100,100
文章图片
扫码关注,回复【面试】获取面试宝典
文章图片
推荐阅读
- mysql|InnoDB数据页结构
- javaweb|基于Servlet+jsp+mysql开发javaWeb学生成绩管理系统
- mysql|一文深入理解mysql
- Java毕业设计项目实战篇|Java项目:在线嘿嘿网盘系统设计和实现(java+Springboot+ssm+mysql+maven)
- SQL|SQL基本功(五)--函数、谓词、CASE表达式
- vue|电商后台管理系统(vue+python|node.js)
- Java及基础算法及数据结构|旧笔记整理(MySQL)
- mysql|双非本211硕,无实习无项目,自学大数据开发,秋招上岸
- 数据库|Mysql--InnoDB存储引擎详解
- MySQL学习笔记-9-order by