MVCC的目的是为了实现多版本的并发控制,就是为了解决读写冲突.而他的实现原理,主要依赖记录中的隐式字段,undoLog,Read View
- 隐式字段
DB_TRX_ID (6byte),最近修改(修改/插入)事务ID:记录创建记录/最后一次修改记录的事务ID
DB_ROLL_PTR(7byte)回滚指针,执行记录的上一个版本
DB_ROW_ID(6byte):隐含的自增ID,如果数据表没有主键,innoDB会自动生成DB_ROW_ID产生一个聚簇索引
delete_flag(删除标识)记录被更新或删除并不代表真的删除,只是flag变了
- undoLog
insert undo log:代表事务在insert新纪录的时候产生的undo log;用于回滚,事务提交以后可以丢弃。
update undo log:事务在进行update或delete会生产log。不仅在事务回滚时需要,在快照读的时候也需要。不能随意删除,只能在快速读胡总和事务回滚不涉及到该日志的时候才会被purge线程统一删除
- insert一条记录,id为1,name为张三,隐式主键为1,事务ID和回滚ID为null
- 事务A对记录进行修改,修改改行数据的时候,最先把改行加上排他锁,然后把数据拷贝到undo log ,改完以后修改隐式事务ID,默认为1,之后递增,回滚指向之前的拷贝记录①,事务提交以后释放锁
- 事务B继续修改同一条数据,先加排他锁,然后把数据保存到undo log中,因为已经有undo log了,所以将当前的undoLog作为链表的表头,加入到undoLog最前面②。事务提交,释放锁
同一条记录,多次更新操作以后,会在undoLog中形成多个记录。innoDB不会真的形成多个版本的数据,只是因为借助了undoLog的每次写操作的反向操作,所以索引上只会有一个版本(最新的版本)因为反向操作,所以看起来像是多个版本
- ReadView
ReadView是事务进行快照读的时候生成的读视图,,事务在执行快照读的时候,会生成数据库当前的一个快照,记录并维护系统当前活跃事务的ID(每个事务开启的时候都会生成一个ID,ID是递增的,所以越新的事务,ID越大)
ReadView在被事务读取的时候,可能是数据库的最新数据,也有可能是undolog中的历史数据。
可见性算法:
首先有四个概念:
当前事务ID,current_trx_id
active_trx_list,生产视图时正在活跃的事务ID集合
min_trx_id 活跃事务里最小的事务ID
next_max_trx_id 活跃事务里最大的事务ID的下一个事务ID
- 比较最小活跃事务和当前事务ID,如果current_trx_id<min_trx_id,则当前事务可以可查看该事务,说明修改这条数据的事务在生产readView已提交,可见,如果大于,则进入下一个判断
- if current_trx_id>next_max_trx_id,说明该数据在生成ReadView的时候还未启动,则不可见
- if active_trx_list.contains(current_trx_id) 说明ReadView生产的时候,当前事务还未提交,不可见
MVCC流程
- 当事务2对某行数据执行了快照读,数据库会对改行数据生产一个ReadView读视图,假设当前事务Id为2,事务1和事务3还在活跃中,事务4在事务2快照读前一刻提交了
事务1 | 事务2 | 事务3 | 事务4 |
事务开始 | 事务开始 | 事务开始 | 事务开始 |
进行中 | 进行中 | 进行中 | 修改且提交 |
进行中 | 快照读 | 进行中 |
current_trx_id=2,trx_list中=[1,3],min_trx_id=1,next_max_trx_id=5
事务 | 字段 | DB_ROW_ID | DB_TRX_ID | DB_ROLL_PTR(回滚指针) |
2 | A | 1 | 4 |
事务2快照读的时候,事务ID已经时4,则4>min_trx_id,不符合,4<next_max_id,不符合,4不包含于trx_list,符合条件,可读。
关于undoLog是否会被竞争
为了提高undoLog的写入能力,普通表和临时表都各有一条insert和update的undoLog,所以一条事务最多有4条undoLog
以上资料总结来源于网络