什么是MVCC
- 多版本并发控制,用于
**解决读写冲突**
,在一个数据行正在进行写操作时,另外的事务想要可以**读取以前版本的数据**
,从而不会被阻塞,提高并发效率。 **快照读**
不会进行阻塞,读取的是**旧版本的数据**
,使用方法即**普通的查询**
。**当前读**
会**阻塞**
直到获取到数据行的**共享锁**
,读取的是**新版本的数据**
。
SELECT * FROM A FOR UPDATE
SELECT * FROM A LOCK IN SHARE MODE
INSERT
DELETE
UPDATE
MVCC实现原理
在行对象中,包含**三个隐藏字段**
。
**DB_TRX_ID**
最后一次修改数据列的事务ID**DB_ROLL_PTR**
回滚Undo Log指针链表- 当没有主键时,该列作为
**主键列**
。
旧版本数据的存储来源
**Undo Log**
是先前事务操作的**逻辑逆操作**
语句,在Undo Log回滚指针链表中,事务对数据行进行**修改操作**
时,都会生成**Undo Log**
,以**头插法**
的形式插入到**回滚指针链表**
中,保证新生成的UndoLog在**链表的头部**
。Undo Log与行对象一样包含两个属性,**生成该Undo Log的事务Id**
,**指向下一个Undo Log日志的指针**
。因此当某个事务在进行该数据行的写操作时,其他的事务在读取该数据行时,可以根据**事务Id**
和Undo Log 进行SQL语句的回滚,从而获取到以前版本的数据。因为是头插法插入,Undo Log的生成时间从新到旧。如果查询到**最后一条**
Undo Log日志,事务Id还不匹配,则说明该列不能被当前事务所看到。
如何判断Undo Log是否满足条件
事务在进行读取操作时,会先获取到**Read View**
,根据这里的信息来判断数据行中哪些Undo Log满足条件。
Read View中包含以下内容。
- 创建
**当前**
Read View的**事务Id**
- 当前活跃的
**事务Id数组**
,即事务为活动中状态,并且没有进行提交。 - 当前活跃的事务中,
**Id最小的事务**
**系统中下一个事务的分配值**
,**大于**
所有活跃事物Id
Read View结合Undo Log的判断方法
- 如果
**当前Undo Log**
的事务Id**小于**
Read View中的最小id,则说明当前数据行的最后修改事务**已经提交**
,说明该条符合条件。 - 如果
**当前Undo Log**
的事务Id**大于等于**
Read View中的**下一个分配值**
,则说明当前数据行的事务是在当前 Read View**生成之后才创建的**
,说明该条不符合条件。 - 如果当前Undo Log的事务Id大于等于最小Id,并且小于事务最大Id,则需要在判断该
**数据行的事务Id是否在活跃事物的列表中**
,如果该数据列的事务**在活跃事务的列表**
中,说明该事务**没有提交**
,说明该条不符合条件,如果**不在活跃事务的列表**
中,则说明该**事务已经提交**
,符合条件。 - 如果该条数据行不符合条件,则会根据
**UndoLog版本链继续向下查询**
,如果在查询过程中发现符合条件的则返回,如果直至找到链表的**末尾仍不符合条件**
,则说明该事务**不能**
看到该条数据行。
读已提交和可重复读的区别
- 在
**读已提交**
隔离级别中,开启事务后**每一次**
进行数据的读取都会**重新读取一次 Read View数据**
。因此在其他的事务进行提交后,该事务的id会从Read View中的活跃事务Id中移除,可以读取到其他事务已提交的数据。 - 在
**可重复读**
隔离级别中,开启事务后**只有第一次进行数据的读取才会读取一次 Read View数据**
。在其他的事务进行提交后,会有更多的Undo Log往该数据行的回滚日志中进行插入,但是Read View中的活跃事务Id表没有改变,因此不会读取到其他事务已提交的数据,保证了可重复读的特性。
MVCC如何解决幻读
- 在
**可重复读**
隔离级别中可以**解决部分幻读**
的问题 - 在
**可重复读**
隔离级别中,读取数据时**只会在第一次查询时**
生成Read View。 - 假设该事务A在第一次读取了一部分数据,此时事务B插入了一些数据,并且提交事务。
- 如果在事务A创建Read View时,
**事务B已经开启**
,则事务B处于**事务活跃的Id列表**
中,此时事务A不会读取到事务B的新插入的数据。 - 如果在事务A创建Read View时,
**事务B还未开启**
,则事务B的事务Id大于事务A**Read View存储的下一个分配的最大事务Id**
,此时事务A也不会读取到事务B新插入的数据 - MVCC
**不能完全解决幻读**
,在事务A通过update并且where没有条件时,**修改了事务B刚插入的记录**
,此时事务A中可以读取到事务B的这条记录,并且是被事务A修改过的
MVCC总结
- 数据行中删除仅作为标记位,目的是为了给MVCC提供服务,要保留数据行的历史数据。
- MVCC解决了读写冲突的问题
- 因为减少了锁的使用,降低了死锁的概率
- 解决了快照读的问题,可以查询到之前某个时间节点的数据行快照信息