【MySQL】版本并发控制MVCC
一致性视图
MySQL
中,有两个视图的概念:
-
一个是
view
,它是用查询语句定义的虚拟表,在调用时执行查询语句,并且生成结果。 -
另一个是
InnoDB
在实现MVCC
时用到的一致性读视图,用于支持RC
和RR
隔离级别。
对隔离级别的影响
隔离级别是利用一致性视图来实现的:
-
可重复读隔离级别下,视图是在事务启动时创建的,整个事务存在期间都用这个视图。
-
读提交级别下,视图是在每个
SQL
语句开始执行时创建的。 -
读未提交级别下,直接返回记录上的最新值,没有视图概念。
-
串行化级别下,通过直接加锁的方式避免并行访问。
事务启动时机
begin/start transaction
并不是一个事务的启动时间,只有执行到它们之后第一个操作InnoDB
表的语句时,事务才启动。
对于可重复读隔离级别,一致性视图是在执行第一个快照读语句时创建。也可以使用start transaction with consistent snapshot
启动一个事务,同时立即创建一个一致性视图。
如果autocommit=1
,则每个语句就是一个事务,InnoDB
会隐式执行begin
和commit
。
事务ID
InnoDB
的每个事务有一个唯一的事务ID,叫transaction id
。事务开始时申请,严格递增。
每行数据都有多个版本,每个版本有自己的row trx_id
。事务更新数据时,会生成新的数据版本,并且把transaction id
赋值给这个版本的row trx_id
。
视图数组
视图数组里保存的是,启动了但是还没提交的事务ID。
数组里事务ID最小值称为低水位,系统已经创建过的事务ID最大值称为高水位。
视图数组和高水位,组成了当前事务的一致性视图(read-view)
。数据版本的可见性,是基于数据的row trx_id
和一致性视图对比得到。
InnoDB
就是这样利用所有数据都有多个版本的特性,实现了秒级创建快照的能力。
数据可见性
对于某个数据版本的row trx_id
:
-
如果等于当前事务的事务ID,当前事务可见。
-
如果小于低水位,当前事务可见。
-
如果大于高水位,当前事务不可见。
-
如果不小于低水位不大于高水位,且不在视图数组中,当前事务可见。
-
如果不小于低水位不大于高水位,且在视图数组中,不可见。
总结就是,对于一个事务视图:
-
自己的更新总可见。
-
版本未提交,不可见。
-
版本已提交,但是是在视图创建后提交,不可见。
-
版本已提交,而且是在视图创建前提交,可见。
MVCC
回滚日志
每条记录更新时,都会记录一条回滚日志。通过回滚日志,可以得到前一个状态的值。
MVCC
同一条记录在系统中可以存在多个版本,这就是版本并发控制(MVCC
)。
不同时刻启动的事务有不同的一致性视图,一致性视图结合回滚日志实现数据的版本并发控制。
通过一致性视图判断当前版本数据是否可见,如果不可见,就使用回滚日志查找它的上一个版本。
当没有事务需要用到这些回滚日志时,即没有比这个回滚日志更早的视图,回滚日志会被清除。
当前读
当前读总是读取已经提交完成的最新版本,并且会对读取的记录加锁。
更新数据时都是先读后写的,而且只能读当前值,此时的读是当前读。
查询数据时,如果对select
语句显式加锁,则也是当前读。加锁逻辑为:
-
lock in share mode
加读锁(S
锁,共享锁)。 -
for update
加写锁(X
锁,排他锁)。
当前读的实现
当前读是依靠next-key
锁来实现的。
快照读
快照读总是读取记录的可见版本,不用加锁。如简单的select
语句即为快照读。
快照读的实现
快照读依靠MVCC
和undo log
来实现的。