这是我参与「第五届青训营 」笔记创作活动的第1天
什么是MVCC?
MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
MVCC 在 MySQL InnoDB 中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读写。
MVCC多版本并发控制是 【维持一个数据的多个版本,使得读写操作没有冲突】 的概念,它只是一个抽象概念,并非实现。
因为MVCC只是一个抽象概念,要实现这么一个概念,MySQL就要实现具体功能去实现它,比如说快照读就是MVCC的一个具体实现。
数据库并发场景有三种,分别为:
- 读-读 这种场景不存在任何问题,也不需要并发控制
- 读-写 会有线程安全问题,会可能造成脏读、幻读、不可重复度这种问题
- 写-写 会有更新丢失问题,比如说第一类更新丢失,第二类更新丢失
所以MVCC是用来解决读-写冲突的无锁并发控制。
所以在数据库中,因为有了 MVCC,所以我们可以形成两个组合:
- MVCC+悲观锁 MVCC解决读写冲突,悲观锁解决写写冲突
- MVCC+乐观锁 MVCC解决读写冲突,乐观锁解决写写冲突
这样的组合可以最大程度的提高数据库并发性能。
MVCC的实现原理:
InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。 它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。 每行数据也是有多个版本的,每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。 同时,就得原数据版本要保留,并且在新的数据版本中,有一个回滚指针,指向这个记录的上一个版本(记录存储在undo log里)。记录在更新之后数据库会把旧数据记录到undo log里,记录是一条链表,链首就是最新的旧记录,链尾就是最早的旧记录。
在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,有一个数组(由 Read View 维护),记录并维护系统当前活跃事务的 ID (活跃:正在执行但是未提交的事务)
低水位 记录列表里事务ID最小的ID。
高水位 记录当前出现过事务ID的最大值+1
然后后面读取数据的时候会首先比较数据版本的事务ID是否小于低水位,如果小于,那么说明在我生成ReadView之前这个事务肯定已经被提交了,当前事务能够看到该记录,直接读取。
接下来判断数据当前版本是否大于等于高水位 ,如果是的话那么事务肯定是在readView生成之后才出现的,也就是说对于该数据修改的事务肯定是在readView生成之后才出现的,那这条数据对当前事务肯定是不可见的,要通过回滚指针读取旧版本的数据。
如果低于高水位、大于等于低水位 则进入下一个判断
判断该数据版本的事务ID是否在活跃事务(数组)中,如果在,则代表我ReadView生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的,所以这个数据版本对我当前事务来说是看不见的,需要通过undo log回退版本。
如果不在,则说明你这个事务在Read View生成之前就Commit了,你修改的结果,我当前事务是看得见的。
在 RC (读已提交)隔离级别下,是每个快照读都会生成并获取最新的 Read View;而在 RR (可重复度)隔离级别下,则是同一个事务中的第一个快照读才会创建 Read View, 之后的快照读获取的都是同一个 Read View。