什么是MVCC?
Multi-Version Concurrency Control 多版本并发控制,是一种基于无锁思想
的并发控制方法
MVCC维持一个数据的多个版本,使得读写操作没有冲突
MVCC通过快照读
,可以在可重复读
隔离级别下解决幻读
。因为每次读当前快照,表中数据行数发生变化也不会受到影响。
MVCC 只在读已提交
和可重复读
两个隔离级别下工作。
其他两个隔离级别都和MVCC不兼容:
因为读未提交总是读取最新
的数据行,而不是符合当前事务版本
的数据行,所以不需要MVCC
。
而 串行化则会对所有读取的行都加锁,也不需要MVCC
。
当前读和快照读
什么是快照读?
事务在执行读取操作时会生成一个快照,之后即使其他的事务修改了表中的数据,读取操作仍然读取这个快照
,而不是去读取最新的数据。因此实现了可重复读和幻读
。
什么是当前读?
当前读就是读取当前数据库中最新的数据内容
。
什么情况下是快照读?
所有普通的SELECT语句在读已提交和可重复读
隔离级别下都算是快照读。
什么情况下是当前读?
除了上面的普通select,其他都是当前读,例如:select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)
等这些操作都是当前读
快照是什么时候生成的呢?
事务启动之后,第一次执行select时生成
MVCC的实现原理
快照
快照是什么
在该事务执行快照读的那一刻,会生成数据库系统当前的一个快照,快照里记录系统当前活跃事务的ID
(活跃事务是指已启动但未提交的事务,事务ID是事务的唯一标识,是按申请顺序严格递增
的)。
快照的作用?
快照主要是用来做可见性判断
的,判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log
里面的之前某个版本的数据。
快照工作原理:
因为每行数据都有多个版本。每次事务更新数据的时候,都会生成一个新的版本,旧的数据版本也要保留(利用undo log保留旧版本)
下图就是一个记录被多个事务连续更新后的状态。
因此,一个事务只需要在启动的时候声明说:
以我启动的时刻为准,如果一个数据版本
是在我启动之前生成的,就认;
如果是我启动以后才生成的,我就不认,我必须要找到它的上一个版本。如果上一个版本也不可见,那就得继续往前找,直到直到我启动之前就生成好的版本。
当然,如果是我自己更新的数据,我还是要认的。
上面的这些功能是怎么实现的呢?
InnoDB 为每个事务构造了一个数组
,用来保存这个事务启动瞬间,当前正在活跃的所有事务 ID
。
数组里面事务 ID 的最小值记为低水位
,当前系统里面已经创建过的事务 ID 的最大值加 1
记为高水位
。
而数据版本的可见性规则,就是基于低水位和高水位来划分的,如下图:
这样,对于当前事务的启动瞬间来说,一个数据版本的事务ID,有以下几种可能:
- 如果落在
绿色
部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见
的; - 如果落在
红色
部分,表示这个版本是由将来启动的事务生成的,是肯定不可见
的; - 如果落在
黄色
部分,那就包括两种情况
a. 若 事务ID 在数组中,表示这个版本是由还没提交的事务
生成的,不可见
;
b. 若 事务ID不在数组中,表示这个版本是已经提交了的事务
生成的,可见
。
总结:
对于一个快照来说,事务它够读到哪些版本数据,要遵循以下规则:
当前事务内的更新,可以读到;
版本未提交,不能读到;
版本已提交,但是却在快照创建后提交的,不能读到;
版本已提交,且是在快照创建前提交的,可以读到;
undo log链表
每次记录更新前,都会将旧值放到一条undo日志
中,就算是该记录的一个旧版本
,随着更新次数的增多,所有的版本都会被连接成一个链表,我们把这个链表称之为版本链
如下图
如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本
。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见
,查询结果就不包含该记录。
读已提交和可重复读的原理区别
二者的本质区别就是它们生成快照的时机不同
。
可重复读是只在事务开始的时候,生成一个当前事务全局性的快照,即只生成一次。重复读到的数据都是这个快照的,所以可重复读。
读已提交则是每次执行语句的时候都重新生成一次快照,每次生成都会把之前的记录丢掉,所以无法重复读。