MVCC详解
MVCC也就是多版本并发控制,也就是一种并发控制的方法
快照读和当前读
当前读是指比如共享锁和独占锁这些操作,读取的是最新的版本,保证其他事务不能修改当前事务,对读取事务进行加锁
快照锁是指不进行加(读写)锁的非阻塞的操作,一定不是可串行的隔离级别,因为可串行的级别下会退化成当前读,也就是阻塞操作,快照读是为了提高并发性能,基于MVCC,避免加锁的开销,也就是基于多版本,相当于一个行锁的变种。
MVCC也就解决了读写冲突时不用加锁的问题,当前读是悲观锁
数据库并发场景有三种,分别为:
读-读:不存在任何问题,也不需要并发控制读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
解决为了读写冲突,为事务分配单项增长的时间戳,每一个修改都会保存一个版本,版本与事务的时间戳相对应,读操作只读该事务开始前的快照,也就是产生了事务隔离
MVCC + 悲观锁
MVCC解决读写冲突,悲观锁解决写写冲突MVCC + 乐观锁
MVCC 解决读写冲突,乐观锁解决写写冲突
我们自己加锁都是解决的并发写的操作,并发读写的操作由MVCC解决,写写还是要加锁
实现原理:(读未提交和可串行化不支持MVCC)
实现原理是依赖于记录中的三个隐式字段(数据库定义的)、undo日志、Read View
三个字段(也就相当于版本链):
DB_TRX_ID:
6 byte,最近修改(修改/插入)事务 ID:记录创建这条记录/最后一次修改该记录的事务 ID,事务的id一定是自增的(但是不一定是连续的),这样才能判断事务的执行顺序,事务的id不是由开启的时间决定的,而是事务对该数据进行修改操作的时间决定的,正是因为只能读之前或现在的,才实现了并发情况下其他的事务对数据的操作不会影响到可重复读和读已提交,因为不关注版本链后面的
DB_ROLL_PTR:
7 byte,回滚指针,指向这条记录的上一个版本(存储于 rollback segment 里),用于配合 undo日志,指向上一个旧版本
DB_ROW_ID:
6 byte,隐含的自增 ID(隐藏主键),如果数据表没有主键,InnoDB 会自动以DB_ROW_ID产生一个聚簇索引
- 实际还有一个删除 flag 隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除 flag 变了

undo日志:
undo log 主要分为两种:
insert undo log
1 | 代表事务在 insert 新记录时产生的 undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃 |
update undo log
1 | 事务在进行 update 或 delete 时产生的 undo log ; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一清除 |
版本链的长度不是无限的,对于老的数据就可以进行清理,当前活跃的事务和该事务回滚指向的前一个事务所操作的该数据的历史记录是不能删除的,因为如果当前事务执行失败了得有地方回滚
对于新事务对一条数据被旧事务所操作的记录是否可见的情况,要分为三种
我们将当前执行的事务的id假设为curr_id,那么版本链中的事务id也就是trx_id
所以当一个事务开始时,应该先去申请它的一个id,在版本链上查看,版本链上的事务id(即trx_id)如果比当前事务的id大的,那后面的这些数据版本一定是不可见的,最多只能看到之前和现在的。
如果相等那一定是可见的,因为我自己写的东西不可能我自己读不出来,而且这个trx_id就是当前事务,是当前事务操作的
如果小于当前事务id所操作的记录(当然没被清除的情况下)的,如果事务已经commit了,那就能看到,如果版本链的(前面的)事务,还没commit说明事务还正在运行,那么就看不到,如果是读已提交,那么就会生成新的ReadView
这里要分隔离级别进行讨论:
可重复读:同一个事务,每次select 的readview 是 不变的。所以可以实现可重复读。
读已提交:同一事务,每次select 都会去获取新的readview,所以根据版本链比对规则会把已提交的,感知到,并可以读到。
在版本链中一定能找到一个已经提交了的记录,因为只有commit的才能被读取到并且作为回滚对象,并且只有创建数据的事务提交了我们后面的事务才对其进行版本链查找或操作
ReadView是用来判断事务id是不是更好的符合我们隔离级别所设置的条件,ReadView里包括最小活跃事务id(小于则一定已提交)、当前事务id(大于一定是后面的,所以不可见),还有最小事务id和当前事务之间的id的集合,如果是可重复读,ReadView相当于尺子,后面不再生成新的,所以每一次都会读到同样的记录,实现可重复读
MVCC同时解决了部分幻读的问题,部分是因为只有快照读才行,如果是当前读那么就会重新生成ReadView
如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !