1. 概念

MVCC(Multi-Version Concurrent Control)多版本并发控制,在Mysql Innodb中主要是用于提高数据库并发能力,做到存在读写冲突时,也能不加锁非阻塞并发读

2. 当前读、快照读

类型

概念

当前读

当前读即为每次读取的数据均为最新数据,如select … lock in share mode(共享锁)、select … for update、update、delete(排它锁)等均为当前读,因为他们读取的均为记录的最新数据,且在读取时会对该行数据进行加锁。

快照读

不加锁的select为快照读。此时的数据库隔离级别必须是非串行级别,否则会退化为当前读。快照读基于Read View(可读视图)实现,所以它读取到的数据不一定是当前最新数据,而可能是历史数据。

3. 工作原理

MVCC工作原理主要依赖:

  1. 数据行中的三个隐藏字段DB_TX_ID、DB_ROLL_PTR、DB_ROW_ID
  2. Undo Log
  3. Read View(可读视图,见下一小节)

MVCC仅在隔离级别:读已提交、不可重复读下发挥作用。

3.1 隐藏字段图解

mysql read_buffer_size 如何设置 mysql read view_隔离级别


特别说明回滚指针DB_ROLL_PTR的应用:

mysql read_buffer_size 如何设置 mysql read view_数据库_02

3.2 Read View可读视图

Read View概念

Read View是事务在进行快照读时产生的读视图(即select时生成)。

Read View中存在以下三个关键变量:

mysql read_buffer_size 如何设置 mysql read view_mysql_03


这里需要说明的是,事务ID是自动递增的,也就是说下一次产生的事务比上一次产生的事务ID要来的大,可按平时设计的表中的主键ID自动递增理解。图中的low_limit_id表示尚未分配的事务ID,但是下一个产生的事务的ID就是他。举例说明

mysql read_buffer_size 如何设置 mysql read view_database_04

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的数据行在数据库中的形式):

mysql read_buffer_size 如何设置 mysql read view_mysql_05

经事务2执行Update并Commit后,数据行发生如下变化

mysql read_buffer_size 如何设置 mysql read view_mysql_06


此时事务1再次SELECT(操作5),再次生成Read View结果如下:

mysql read_buffer_size 如何设置 mysql read view_隔离级别_07


根据Read View判断条件3可知,事务ID为2的数据行对事务1可见。若在事务2进行Update后,并未Commit,而此时事务1进行SELECT,则Read View结果如下:

mysql read_buffer_size 如何设置 mysql read view_database_08


此时根据数据行事务ID为2进行判断,事务2仍在活跃列表[trx_list]中,表示当前事务并未提交,则该行数据行对当前事务不可见。

Read View在【可重复读】隔离级别下的使用

在事务1第一次SELECT(操作1)时,生成的Read View结果如下:

mysql read_buffer_size 如何设置 mysql read view_database_08


尽管经事务2执行Update并Commit后,数据行变为以下内容:

mysql read_buffer_size 如何设置 mysql read view_mysql_06


但是当事务1执行第二次SELECT(操作5)时,可重复读隔离级别多次SELECT均复用第一次生成的Read View结果。则此时的Read View中活跃列表[trx_list]中仍存在事务2,所以事务ID为2的数据行对事务1不可见,则顺着版本链向下遍历,找到事务ID为0的数据行,该数据行满足Read View判断条件1,所以对事务1可见,SELECT查询结果集中包含该行数据行。