MVCC多版本并发控制
没接触MVCC之前我们解决幻读问题是通过加间隙锁或者临键锁来实现的,加锁势必会造成事务的堵塞,大幅降低性能。通过mvcc我们就可以不加锁来解决幻读问题,除了解决幻读问题之外,我们之前解决不可重复读,读未提交也都是通过加锁来实现的,这些问题通过mvcc也都可以解决
MVCC只在Read committed(读已提交),Repeatable read(可重复读)的事务隔离级别下起作用
隐藏字段,Undo Log版本链
在行格式中,隐藏字段一个包含三个
1,row_id 用于既没有主键又没有唯一非空索引的情况下作为主键创建B+树
2,transaction_id(trx_id) 当有事务对索引的记录改动时都会记录这个事务的id
3,roll_pointer 当记录发生改动时会把旧的版本写道undo日志中roll_pointer用来指向之前的记录
MVCC实现原理
MVCC基于隐藏字段,Undo Log版本链,ReadView
什么是ReadView
在MVCC中多个事务对同一条记录的更新都会产生一个历史快照,这些快照在undio日志中通过roll_pointer形成一个Undo Log日志链,我们在读取数据的时候具体读到那个历史快照就由ReadView决定了
在我们进行读操作的时候就会启用MVCC机制然后产生ReadView(只在Read committed,Repeatable read隔离级别下)
ReadView的组成
1,creator_trx_id 创建ReadView的事务id
只有在执行update,delete,insert这些操作的时候才会分配事务id select操作则是默认id=0
2,trx_ids 活跃事务id的列表 列表数据包括在生成ReadView的时候当前系统中活跃的事务id
活跃事务:就是已经开始但是还没有结束的事务
3,up_limit_id 活跃的事务中最小事务id
4,low_limit_id 在生成ReadView的时候下一事务的id
假如当前系统中id未1,2,3,4其中1,2,3为活跃事务那么low_limit_id就为5
ReadView的访问机制
我们在进行查询操作时生成当前事务的ReadView然后将匹配到的记录与当前事务的ReadView中的creator_trx_id,up_limit_id,low_limit_id进行比较 比较规则如下
1,如果匹配到的记录中的事务id匹配到 当前事务的ReadView中的creator_trx_id那么就是当前事务匹配到了自己可以读取
2,如果匹配到的记录的事务id小于当前事务的ReadView中的up_limit_id那么这条记录的事务已经提交了这条记录可以访问
3,如果匹配到的记录的事务id大于或者等于当前事务的ReadView中的low_limit_id这说明这条记录是在查询事务开始后才出现的那么不可以访问(这其实就解决了幻读)
4,如果匹配到的记录的事务id在up_limit_id和low_limit_id之间需要判断配到的记录的id是否在trx_ids列中
如果在说明匹配到的这个记录的事务还是活跃状态不能访问
如果不在说明匹配到的这个记录的事务已经提交了可以访问
当前事务的ReadView
匹配到的记录
ReadView的生成问题
我们已知ReadView会在每次进行查询操作的时候都会生成一次但在不同的隔离级别中生成策略是不一样的
在Read committed的隔离级别中,在当前事务中每一次查询操作都会生成新的readview这样就会保证readview中的trx_ids,up_limit_id ,low_limit_id都会最新的,但这样就会出现不可重复读和幻读这也就是Read committed这个隔离级别为什么解决不了 不可重复读和幻读
在Repeatable read的隔离级别中,在当前事务中只会在第一查询操作时生成readview这样就解决了不可重读和幻读
总结
在Read committed和Repeatable read中读操作通过mvcc来保证事务隔离级别而写操作则是通过加锁实现的