1. 概念
MVCC(Multi-Version Concurrent Control)多版本并发控制,在Mysql Innodb中主要是用于提高数据库并发能力,做到存在读写冲突时,也能不加锁非阻塞并发读。
2. 当前读、快照读
类型 | 概念 |
当前读 | 当前读即为每次读取的数据均为最新数据,如select … lock in share mode(共享锁)、select … for update、update、delete(排它锁)等均为当前读,因为他们读取的均为记录的最新数据,且在读取时会对该行数据进行加锁。 |
快照读 | 不加锁的select为快照读。此时的数据库隔离级别必须是非串行级别,否则会退化为当前读。快照读基于Read View(可读视图)实现,所以它读取到的数据不一定是当前最新数据,而可能是历史数据。 |
3. 工作原理
MVCC工作原理主要依赖:
- 数据行中的三个隐藏字段DB_TX_ID、DB_ROLL_PTR、DB_ROW_ID
- Undo Log
- Read View(可读视图,见下一小节)
MVCC仅在隔离级别:读已提交、不可重复读下发挥作用。
3.1 隐藏字段图解
特别说明回滚指针DB_ROLL_PTR的应用:
3.2 Read View可读视图
Read View概念
Read View是事务在进行快照读时产生的读视图(即select时生成)。
Read View中存在以下三个关键变量:
这里需要说明的是,事务ID是自动递增的,也就是说下一次产生的事务比上一次产生的事务ID要来的大,可按平时设计的表中的主键ID自动递增理解。图中的low_limit_id表示尚未分配的事务ID,但是下一个产生的事务的ID就是他。举例说明:
Read View判断条件
Read View判断某行数据对当前事务是否可见,按以下步骤进行判断:
1 | 若数据行的事务ID[DB_TX_ID] < = 集合最小事务ID[up_limit_id],则表示该数据行在当前事务开启前(等于的情况则表示是当前事务修改的记录,肯定可见)就已经提交(产生),对当前事务可见 |
2 | 若数据行的事务ID[DB_TX_ID] > = 下一事务ID[low_limit_id],则表示该数据行是当前事务开启之后产生,则对当前事务不可见 |
3 | 如果1,2条件均不满足,则判断集合最小事务ID[up_limit_id]<=数据行的事务ID <= 下一事务ID[low_limit_id]范围内,如果满足,则需要判断数据行事务ID是否存在集合事务ID[trx_list]中,如果存在,则表示产生该行数据的事务扔处于活跃状态,事务并未提交,该行数据对当前事务不可见。否则表示该数据行已经提交事务,对当前事务可见 |
4 | 如果该数据行对当前事务不可见,则顺着版本链向下遍历,重复1,2,3,4步骤直到找到对应的版本数据行,若未找到,则SELECT查询结果集为空 |
前面说过 MVCC在隔离级别 读已提交/可重复读 两个隔离级别下才生效
同时还有以下区别:
读已提交:每次SELECT时均产生一个最新的Read View
可重复读:每次SELECT均复用第一次SELECT产生的Read View
Read View在【读已提交】隔离级别下的使用
理解了上面的图示后,就可以理解两个事务共同存在时,A事务可以读取到B事务已提交的数据:
(当前隔离级别为 读已提交)
事务1 | 事务2 | |
操作1 | 事务开启:BEGIN | 事务开启:BEGIN |
操作2 | SELECT | |
操作3 | UPDATE | |
操作4 | COMMIT | |
操作5 | SELECT |
问:在读已提交的隔离级别下,事务1操作2和操作5的SELECT结果是否一致?
答案是明显的,事务1操作5的SELECT是事务2更新后的结果,所以两次SELECT结果不一致。
解释:
在事务1操作2时,进行了一次快照读SELECT,此时产生一个Read View,如图(包含了select的数据行在数据库中的形式):
经事务2执行Update并Commit后,数据行发生如下变化
此时事务1再次SELECT(操作5),再次生成Read View结果如下:
根据Read View判断条件3可知,事务ID为2的数据行对事务1可见。若在事务2进行Update后,并未Commit,而此时事务1进行SELECT,则Read View结果如下:
此时根据数据行事务ID为2进行判断,事务2仍在活跃列表[trx_list]中,表示当前事务并未提交,则该行数据行对当前事务不可见。
Read View在【可重复读】隔离级别下的使用
在事务1第一次SELECT(操作1)时,生成的Read View结果如下:
尽管经事务2执行Update并Commit后,数据行变为以下内容:
但是当事务1执行第二次SELECT(操作5)时,可重复读隔离级别多次SELECT均复用第一次生成的Read View结果。则此时的Read View中活跃列表[trx_list]中仍存在事务2,所以事务ID为2的数据行对事务1不可见,则顺着版本链向下遍历,找到事务ID为0的数据行,该数据行满足Read View判断条件1,所以对事务1可见,SELECT查询结果集中包含该行数据行。