☰
深入理解MySQL MVCC多版本并发控制:从原理到实践
2026/10/15 8:08:37 网站建设 项目流程

一、引言:为什么需要MVCC?

在数据库系统中,并发控制是保证数据一致性的关键。传统的方式是使用锁,但锁会带来严重的性能问题,因为读操作会阻塞写操作,写操作也会阻塞读操作,导致系统吞吐量下降。为了解决这个问题,MySQL InnoDB 引入了MVCC(Multi-Version Concurrency Control,多版本并发控制)机制。MVCC的核心思想是:读操作不阻塞写操作,写操作也不阻塞读操作。它通过维护数据的多版本,让一个事务在修改数据时,不会直接覆盖旧数据,而是创建一个新版本,从而允许其他事务继续读取旧版本的数据。本文将带你深入理解MVCC的两大核心组件:版本链和Read View。


1.1 什么是MVCC?

MVCC,即多版本并发控制,是一种用于数据库并发控制的方法。它允许多个事务同时读取同一行数据的不同版本,而无需等待其他事务完成。每个事务在开始时,都会获得一个唯一的事务ID(trx_id),并且可以读取到该事务开始时已经提交的数据版本。这种设计极大地提高了数据库的并发性能,特别是在读多写少的场景下。


1.2 MVCC的核心组件

MVCC的实现依赖于两个核心组件:

  1. 版本链:InnoDB为每行数据维护一个版本链,记录了该行的所有历史版本。
  2. Read View:一个事务在读取数据时,会生成一个Read View,用于判断当前事务能够看到哪个版本的数据。


二、MVCC的基石:版本链

版本链是MVCC的基础,它通过在每行数据上添加隐藏列来构建。InnoDB为每一行数据都维护了三个隐藏列:

  • DB_ROW_ID:行ID,如果用户没有定义主键,则InnoDB会使用该隐藏列作为聚簇索引。
  • DB_TRX_ID:插入或更新该行的事务ID。
  • DB_ROLL_PTR:回滚指针,指向该行上一个版本的undo log记录。


当事务对数据进行修改时,InnoDB并不会直接覆盖旧数据,而是将旧数据复制到undo log中,然后创建一个新的数据版本,并通过DB_ROLL_PTR将新版本与旧版本链接起来,形成一个链表结构。


事务T1开始

插入数据行R

行R版本1
DB_TRX_ID=T1
DB_ROLL_PTR=null

事务T2开始

T2更新行R

行R版本2
DB_TRX_ID=T2
DB_ROLL_PTR=指向版本1

事务T3开始

T3再次更新行R

行R版本3
DB_TRX_ID=T3
DB_ROLL_PTR=指向版本2

该图展示了版本链的形成过程。当事务T1插入一行数据时,该行只有一个版本(版本1)。当事务T2更新这行数据时,会创建一个新版本(版本2),并通过回滚指针指向旧版本(版本1)。后续的更新操作会持续在链表头部添加新版本。


三、可见性判断的“眼睛”:Read View

Read View是MVCC中用于判断数据可见性的关键。当一个事务需要读取某一行数据时,它并不会直接读取最新的版本,而是根据其自身的Read View来判断哪个版本的数据是可见的。Read View本身是一个数据结构,它包含以下关键信息:

  • m_ids:一个列表,记录了在生成Read View时,系统中活跃(未提交)的事务ID。
  • min_trx_id:m_ids列表中的最小事务ID。
  • max_trx_id:生成Read View时,系统应该分配给下一个事务的ID(即当前最大事务ID + 1)。
  • creator_trx_id:生成该Read View的事务ID。


ReadView

- m_ids: List<TrxId>

- min_trx_id: TrxId

- max_trx_id: TrxId

- creator_trx_id: TrxId

+ isVisible(trx_id: TrxId) : : boolean

Read View 是一个快照,它记录了生成该快照时,哪些事务是活跃的。通过这个快照,事务可以判断某个数据版本是否对自己可见。


四、MVCC的执行流程:如何读取数据

当一个事务需要读取一行数据时,InnoDB会执行以下步骤:

  1. 获取Read View:根据事务的隔离级别,在适当的时候生成Read View。
  2. 遍历版本链:从最新版本开始,沿着DB_ROLL_PTR指针向前遍历。
  3. 应用可见性规则:对于遍历到的每一个版本,应用以下规则判断其可见性:
  • 如果该版本的DB_TRX_ID等于creator_trx_id,说明是当前事务自己修改的,可见。
  • 如果该版本的DB_TRX_ID小于min_trx_id,说明该事务在生成Read View前就已经提交,可见。
  • 如果该版本的DB_TRX_ID大于等于max_trx_id,说明该事务在生成Read View后才开启,不可见。
  • 如果该版本的DB_TRX_ID在min_trx_id和max_trx_id之间,则需要进一步判断:如果该DB_TRX_ID存在于m_ids列表中,说明该事务在生成Read View时还是活跃的,不可见;否则,说明该事务已经提交,可见。


是否是否是否是否

开始读取数据

获取当前事务的Read View

遍历版本链,从最新版本开始

当前版本trx_id == creator_trx_id?

可见,返回该版本

当前版本trx_id < min_trx_id?

当前版本trx_id >= max_trx_id?

不可见,继续遍历前一版本

trx_id是否在m_ids列表中?

该流程图清晰地展示了MVCC读取数据时的可见性判断逻辑。通过这个逻辑,事务可以找到符合其隔离级别要求的、最“新”的可见数据版本。


五、隔离级别与Read View的生成时机

MVCC的可见性判断规则在不同的事务隔离级别下,其Read View的生成时机是不同的,这直接影响了事务的可见性。


5.1 READ COMMITTED(读已提交)隔离级别

在READ COMMITTED隔离级别下,每次执行SELECT操作时,都会生成一个新的Read View。这意味着,在一个事务中,两次相同的SELECT语句可能会看到不同的数据,因为在这两次查询之间,可能有其他事务提交了新的数据版本。


5.2 REPEATABLE READ(可重复读)隔离级别

在REPEATABLE READ隔离级别下,事务在第一次执行SELECT操作时,会生成一个Read View,并且在整个事务的生命周期内,这个Read View都会被复用。这意味着,在一个事务中,无论执行多少次SELECT操作,看到的数据版本都是一致的,保证了可重复读的特性。


(REPEATABLEREAD)使用同一个ReadView保持数据一致性

事务开始

生成ReadView

事务内所有读操作

事务提交/回滚

状态图展示了在REPEATABLE READ隔离级别下,Read View的生命周期。事务开始时生成一个Read View,并在整个事务期间保持不变,确保了读取数据的一致性。


六、实战演练:一个完整的并发场景

为了更好地理解MVCC,我们来看一个具体的并发场景。假设有三个事务T1、T2和T3,它们对同一行数据进行操作。


数据库事务T3事务T2事务T1数据库事务T3事务T2事务T1T3生成ReadViewm_ids=[20], min=20, max=21, creator=30遍历版本链,发现版本2(trx_id=20)在m_ids中,不可见继续遍历,版本1(trx_id=10) < min(20),可见T3复用之前的ReadView仍然只能看到旧版本开始事务开始事务开始事务插入数据A (trx_id=10)更新数据A (trx_id=20)读取数据A (trx_id=30)返回数据A的旧版本(由T1插入的版本)提交事务提交事务再次读取数据A

时序图描绘了一个在REPEATABLE READ隔离级别下的并发场景。事务T3在开始时生成了一个Read View,包含了当时活跃的事务T2。因此,即使T2后来提交了,T3在整个事务期间都只能看到T1插入的旧版本数据,实现了可重复读。


七、总结与思考

MVCC是InnoDB实现高并发读写的核心技术。通过版本链和Read View的巧妙结合,它在不牺牲数据一致性的前提下,极大地提升了数据库的并发性能。


7.1 核心要点回顾

特性说明
版本链每行数据通过DB_ROLL_PTR链接所有历史版本,构成一个链表。
Read View事务读取数据时的“快照”,包含活跃事务列表和可见性判断规则。
可见性规则基于事务ID和Read View中的信息,判断数据版本是否可见。
隔离级别READ COMMITTED下每次读都生成新Read View;REPEATABLE READ下事务开始时生成一个Read View。


45%30%25%MVCC带来的核心价值减少锁竞争,提升并发保证数据一致性实现非阻塞读

饼图直观地展示了MVCC带来的核心价值。通过允许多个读操作并发进行,MVCC显著减少了锁的争用,从而提升了系统的整体吞吐量,同时保证了事务的隔离性和数据的一致性。理解MVCC的原理,对于优化数据库性能和解决并发问题至关重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询