个人理解篇,不一定对,应付面试的时候看的
MVCC(Multi-Version Concurrency Control)全称多版本并发控制,主要用在隔离模式下的提交读、可重复读模式下,依赖于readview和undolog链
一、readview
1、结构
|----------------|--------------------|
| 字段 | 备注 |
| m_ids | 活跃事务id集合 |
| min_trx_id | 活跃事务id集合中最小事务id |
| max_trx_id | 当前分配给新事务的最大事务id+1 |
| creator_trx_id | 生成readview时,当前事务id |
2、举例
现在系统中开启了事务A、B、C,对应的trx_id分别是1、2、3
此时有个新事务的读操作时,会产生如下readview
|----------------|-------|
| m_ids | 1,2,3 |
| min_trx_id | 1 |
| max_trx_id | 5 |
| creator_trx_id | 4 |
二、undolog链
1、结构
|-------------|---------------------------------|
| 字段 | 备注 |
| trx_id | 事务id |
| roll_point | 上一事务指针,指向上一个undolog节点 |
| key | 操作行记录的主键id |
| type | 执行类型(update、insert、delete) |
| commonvalue | 具体的值,没搞清楚具体是什么,但是新值、旧值、更新逻辑应该都有 |
2、举例
现在系统中开启了事务A、B、C,对应的trx_id分别是1、2、3
此时有个新事务的读操作时,会读取一下undolog链如下
三、操作
1、新事物发起读操作时产生readview,然后从undolog链开始查找并进行如下判断逻辑
if(trx_id == creator_trx_id)访问的是自己的事务,允许访问
if(trx_id < min_trx_id)访问的是已经提交的事务,允许访问
if(trx_id >= max_trx_id)访问的是在生成readview之后的事务,不允许访问
if(min_trx_id <= trx_id < max_trx_id)访问的是活跃中未提交的事务,不允许访问
ps:
1、关于undo链中的举例结构图,其实可以看出,默认在 trx_id=1的条目之前还会有trx_id=0的条目 这个条目是在生成readview时最新的提交数据,在trx_id=3后如果有新事务的话,其实也还有指向trx_id=3的事务,这只是个片段,具体的判断阶段会在这图中这一部分判断的比较集中。
2、关于trx_id=1指向的上一个已提交事务的undolog条目,由mysql来采取一些算法来判断是否存在,一般在被引用的时候都是存在的,不必担心找不到已提交的undolog条目
3、每个undolog条目都是某个表的某一行的数据,如果update修改了10条记录,那么会产生10个undolog条目,同时每个undolog链其实只是某一行的版本变化
四、关于
1、RC和RR区别
mvcc可以在RC(读已提交)和RR(可重复读)中,区别就是RC模式下,每次读都会产生readview,而在可重复读模式下,事务结束之前都是复用事务开始后的第一次读产生的readview
2、RR幻读问题
RR中其实没有完全解决幻读问题,如下场景
2.1表Subject:
场景一:
|-----------------------------------------------------------------------------------|-------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
| 事务A | 事务B | 事务B查询结果 |
| start TRANSACTION; | start TRANSACTION; | |
| | select * from Subject where difficultly = "困难"; | |
| insert into `Subject`(difficultly,type,language) value ('困难','英语','C'); commit; | | |
| | select * from Subject where difficultly = "困难" for update; | |
| | select * from Subject where difficultly = "困难" | |
可以看到第四步的当前读会出现幻读,而第五步的快照读仍然正常
场景二:
|-----------------------------------------------------------------------------------|-------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
| 事务A | 事务B | 事务B查询结果 |
| start TRANSACTION; | start TRANSACTION; | |
| | select * from Subject where difficultly = "困难" for update; | |
| insert into `Subject`(difficultly,type,language) value ('困难','英语','C'); commit; | | |
| | select * from Subject where difficultly = "困难" for update; | |
| | select * from Subject where difficultly = "困难" | |
第三步中事务A在执行插入时会一直无法执行完成,直到事务B中commit后才会执行完成
场景一和场景二区别:
场景一中事务B在事务A插入新数据和提交后执行的当前读,而场景二中事务B在事务A插入新数据和提交前执行的当前读,mysql通过mvcc和临键锁会锁定当前读,这样事务A再插入就插入不了新数据了,就解决了幻读,而场景一是在使用临键锁之前提交的,当前读读的是最新数据,所以会出现幻读