---
title: MVCC-多版本并发控制
published: 2025-08-09
description: ''
image: ''
tags: [MVCC]
category: '中间件 > MySQL'
draft: false 
lang: ''
---


# MVCC
MVCC，也就是多版本并发控制

它的目的是： 提高数据库并发性能，用更好的方式处理读写冲突，也就是即使有读写冲突的时候，也能做到不加锁。


## 并发控制的挑战
在数据库系统中，同时执行的事务可能涉及相同的数据，因此需要一种机制来保证数据的一致性，传统的锁机制可以实现并发控制，但会导致阻塞和死锁等问题。

## 传统锁机制


## 当前读和快照读

### 当前读
在MySQL中，当前读是一种读取数据的操作方式，它可以直接读取最新的数据版本，读取时还要保证其他并发事务不能修改当前记录，会对读取的记录进行加锁，MySQL提供了两种实现当前读的机制：

- 锁定读：
    - 锁定读是一种特殊情况下的当前读方式，在某些场景下使用
    - 在使用锁定读的时候，MySQL会在执行读取操作前获取共享锁或者排他锁，确保数据一致性。
    - 共享锁允许多个事务读取统一数据，而排他锁组织其他事务读取或者写入该数据。
    - 锁定读适用于需要严格控制并发访问的场景，但是由于加锁带来的性能开销较大，所以只在必要的时候才使用。    

![](https://blog.meowrain.cn/api/i/2025/08/09/lsvw6z-1.webp)


![](https://blog.meowrain.cn/api/i/2025/08/09/ltng7m-1.webp)

这种就属于悲观锁实现。

### 快照读
快照读就是在读取数据的时候，读取一个一致性视图中的数据，MySQL通过MVCC机制来支持快照读。

具体而言，每个食物在开始的时候都会创建一个一致性视图，这个一致性视图会记录当前事务开始时已经提交的数据版本。

执行查询的时候，MySQL会根据事务的一致性视图来决定可见的数据版本。只有那些在事务开始之前就已经提交的数据版本才是可见的，未提交或在事务开始后修改的数据则对当前事务不可见。

像不加锁的select操作就是快照读，也就是不加锁的非阻塞读。


- 一致性读：
    - 默认隔离级别下（可重复读），MySQL使用一致性来实现当前读
    - 在事务开始的时候，MySQL会创建一个一致性视图，这个视图反映了事务开始时刻的数据库快照。
    - 在事务执行期间，无论其他事务对数据进行了何种修改，事务始终使用一致性视图来读取数据。
    - 可以保证在同一事务内多次查询返回的结果是一致的.


![](https://blog.meowrain.cn/api/i/2025/08/09/m7rmhb-1.webp)


快照读的前提是隔离级别不是串行级别，在串行级别下，事务之间完全串行执行，快照读会退化为当前读中的加锁读。

MVCC主要就是为了实现读-写冲突不加锁，这个读就是指的快照读，是乐观锁的实现。


# 事务的mvcc机制原理是什么？
MVCC允许多个事务同时读取同一行数据，而不会彼此阻塞，每个事务看到的数据版本是该事务开始时候的数据版本，这意味着，如果其他事务在此期间修改了数据，正在运行的事务仍然看到的是它开始时候的数据状态，从而实现了非阻塞读操作。


对于 `读已提交` 和 `可重复读` 隔离级别的事务来说，它们是通过ReadView来实现的，它们的区别在于创建ReadView的时机不同。
ReadView可以理解为当时的一个快照视图，它记录了在创建时刻可见的数据版本。


读提交隔离级别： 在每个select语句执行前，都会重新生成一个ReadView。每个SELECT生成新的ReadView
只能读到其他事务已提交的版本
不能读到未提交事务的修改
这保证了不会出现"脏读"
但会出现"不可重复读"

可重复读隔离级别： 在事务中，执行第一条select语句的时候，生成一个ReadView，然后整个事务期间都在使用这个ReadView

ReadView有四个重要字段：
- creator_trx_id 创建该Read View的事务的事务id
- m_ids 创建ReadView的时候，当前数据库中活跃且未提交的事务id列表，所谓活跃事务，指的就是启动了但是还没提交的事务
- min_trx_id 创建ReadView的时候当前数据库中活跃且未提交的事务中最小的事务的事务id
- max_trx_id 创建ReadView的时候，当前数据库中应该给下一个事务的id值，也就是全局事务中最大的事务id + 1

对于使用InnoDB存储引擎的数据库表，它的聚簇索引记录中都包含下面两个隐藏列
- trx_id 记录最后修改该行数据的事务的事务id
- roll_pointer 记录该行数据的回滚指针，用于实现MVCC（也就是undo日志）

每次对某条聚簇索引记录进行改动的时候，都会把旧版本的记录写入到undo日志中，然后这个隐藏列是个指针，指向每个旧版本记录，于是就可以通过它找到修改前的记录。

![](https://blog.meowrain.cn/api/i/2025/08/09/plbf8e-1.webp)

一个事务去访问记录的时候，除了自己的更新记录总是可见之外，还有这几种情况：

如果记录的 trx_id 值小于 Read View 中的 min_trx_id 值，表示这个版本的记录是在创建 Read View 前已经提交的事务生成的，所以该版本的记录对当前事务可见。
如果记录的 trx_id 值大于等于 Read View 中的 max_trx_id 值，表示这个版本的记录是在创建 Read View 后才启动的事务生成的，所以该版本的记录对当前事务不可见。
如果记录的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之间，需要判断 trx_id 是否在 m_ids 列表中：
如果记录的 trx_id 在 m_ids 列表中，表示生成该版本记录的活跃事务依然活跃着（还没提交事务），所以该版本的记录对当前事务不可见。
如果记录的 trx_id 不在 m_ids列表中，表示生成该版本记录的活跃事务已经被提交，所以该版本的记录对当前事务可见。
这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC（多版本并发控制）。