MVCC - 多版本并发控制
MVCC,全称 Multi-Version Concurrency Control
,即多版本并发控制。通过维持一个数据的多个版本,来控制并发。
一、MVCC 概述
1、三种并发场景
读-读
:不存在任何问题,也不需要并发控制。读-写
:有并发问题,可能会造成事务隔离性问题,可能遇到脏读
,幻读
,不可重复读
。写-写
:有并发问题,可能会存在更新丢失
、更新覆盖
的问题。
2、当前读 & 快照读
-
当前读
:读取的是记录的最新版本,读取时要加锁,保证其他并发事务不能修改当前记录。(如共享锁和排他锁)
-
快照读
:读取的可能是记录的历史版本,读取时不加锁。
注意:
快照读
在MySQL的串行
隔离级别下会上升为当前读
,即使是select操作也会加锁。
3、MVCC的作用
MVCC就是为了不采用悲观锁这样性能不佳的形式去解决读-写
的并发问题,而提出的解决方案。
- 可以做到读操作不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能(解决了
读-写
并发问题) - 可以解决
脏读
,幻读
,不可重复读
等事务隔离问题,但不能解决更新丢失
的问题(仍存在写-写
并发问题)
4、结合MVCC处理并发问题
因为有了MVCC,所以我们可以形成两个组合:
-
MVCC + 悲观锁:MVCC解决
读-写
的并发问题,悲观锁解决写-写
的并发问题 -
MVCC + 乐观锁:MVCC解决
读-写
的并发问题,乐观锁解决写-写
的并发问题
这种组合的方式,可以最大程度的提高数据库并发性能,并解决数据库的并发问题。
二、MVCC 实现原理
MVCC 在 MySQL 中的具体实现是由 隐式字段
+ Undo Log
+ Read View
完成的。
1、隐式字段
数据库中的每行记录,除了我们自定义的字段外,还有数据库隐式定义的一些字段:
名称 | 字段 | 占用 | 作用 |
---|---|---|---|
隐式主键 | DB_ROW_ID |
6 byte | 如果数据表没有主键,InnoDB会自动根据 DB_ROW_ID 产生一个聚簇索引。 |
事务ID | DB_TRX_ID |
6 byte | 记录 创建这条记录 或 最后一次修改这条记录 的事务ID(即最新的事务ID) |
回滚指针 | DB_ROLL_PTR |
7 byte | 指向这条记录的上一个版本(存储于rollback segment 里) |
2、回滚日志 Undo Log
MVCC 使用到的快照存储在 undo log 中,该日志通过回滚指针把一个数据行(Record)的所有快照连接起来。
如何获取一条记录的历史版本呢?
虚线框里是同一行数据的4个版本,图中的三个虚线箭头U1、U2、U3就是undo log。
- V1、V2、V3并不是物理上真实存在的,而是每次需要的时候,根据
当前版本V4
和undo log
计算出来的。
3、一致性视图 Read View
上一节说到,多个事务对同一行记录进行更新会产生多个历史版本,这些历史版本可以通过 当前版本
和 undo log
计算出来。
但是,如果一个事务要想查询某条行记录,需要读取哪个版本的行记录呢?这时就需要根据 Read View 来进行判断了。
1)什么时候生成?
不同的隔离级别下,生成Read View的时机也是不一样的(MVCC主要解决 RC 和 RR 两种隔离级别)
- 可重复读(RR):
当前事务第一次select时
,创建Read View,之后事务里的其他查询都共用这个Read View。 - 读已提交(RC):
每一个select执行之前
,都会重新创建一个新的Read View。
2)可见性判断
如何通过 Read View 判断当前事务能够看到哪个版本的数据呢?
- 获取行记录的最新事务ID(即
DB_TRX_ID
) - 根据 Read View 的
可见性算法
判断可见性。 - 若不符合可见性,就通过
回滚指针
取出上一版本的事务ID
再进行判断- 遍历版本链的
事务ID
(从链首到链尾,从最新的版本开始判断),直到找到符合可见性的事务ID
。
- 遍历版本链的
- 符合可见性的
事务ID
所在的版本,就是当前事务能看见的最新版本。
3)可见性算法(属性)
// MySQL源码分析
class ReadView {