Mysql中MVCC机制详解

MVCC 是一种并发控制机制,通过保存数据的多个版本,使得 读操作(SELECT)不需要阻塞写操作(UPDATE/DELETE),从而实现高并发下的“读写不冲突”。

MVCC 适用于哪些隔离级别:

隔离级别 是否使用 MVCC 说明
READ UNCOMMITTED 可以读到未提交数据(脏读),不需要 MVCC
READ COMMITTED 每次读都读到最新提交版本
REPEATABLE READ(默认) 同一个事务中多次读到的数据一致
SERIALIZABLE 强制加锁,序列化执行,不用 MVCC

MVCC 的核心机制:隐藏列 + Undo Log + Read View

在 InnoDB 的每一行记录中,都隐含有两个重要的隐藏字段:

字段名 含义
trx_id 最后一次修改该行的事务 ID
roll_pointer 指向 undo log 的指针,用于回溯到旧版本

此外,还有两个核心组件:

1. Undo Log(回滚日志)

  • 每当事务修改数据时,旧版本会被保存在 Undo Log 中。

  • 这样就可以“回滚”或“读取历史版本”。

  • Undo Log 存在系统表空间或独立表空间中。

2. Read View(读视图)

  • 当 InnoDB 做“一致性读(consistent read)/快照读”时,会为该语句(或事务)创建一个 Read View(读视图 / snapshot)。

  • 它决定了当前事务能“看到”哪些版本的数据。

Read View 中包含以下关键字段:

  • m_ids:创建 Read View 时 正在运行(active) 的事务 ID 列表(即那些当时还没提交或回滚的事务)。
  • min_trx_id:最小活跃事务 ID
  • max_trx_id:下一个要分配的事务 ID(比所有已存在事务大 1)

可见性判断机制

条件 结果
记录的 trx_id < min_trx_id ✅ 可见(已提交的旧事务)
记录的 trx_idm_ids ❌ 不可见(仍在活跃事务中)
记录的 trx_idmax_trx_id ❌ 不可见(未来事务)

可是为什么按这三个条件/顺序来设计?

比较直观的伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 参数: row_trx_id = 记录上标记的 trx_id(生成该版本的事务)
# read_view = { min_trx_id, max_trx_id, m_ids, creator_trx_id }
# cur_trx_id = 当前事务的 trx_id

if row_trx_id == cur_trx_id:
return VISIBLE # 当前事务应能看到自己做的修改
if row_trx_id < read_view.min_trx_id:
return VISIBLE # 老版本:一定由早先已提交的事务生成
if row_trx_id >= read_view.max_trx_id:
return NOT_VISIBLE # max_trx_id 是创建 Read View 时系统准备分配的下一个 ID,凡是 ≥ max_trx_id 的事务都是 在 Read View 之后才开始 的。

# 否则 row_trx_id 在 剩下的区间[min_trx_id, max_trx_id)里
# 说明该生成版本对应的事务在创建ReadView时是“可能活跃的”
if row_trx_id in read_view.m_ids:
return NOT_VISIBLE # 那个事务在创建ReadView时还没提交 —— 对快照不可见
else:
return VISIBLE # 不在活跃列表里,说明它已提交(但 id 在范围内)

示例展示

假设系统中事务 ID 单调增长(分配顺序为 100、101、102……):

事务 T1:trx_id = 100,已提交(早先的改动)。
事务 T2:trx_id = 200,正在运行(未提交)。
事务 T3:trx_id = 300,正在运行(未提交)。

此刻某事务 R(要读)创建了 Read View,复制了活跃列表 m_ids = [200, 300],得到 min_trx_id = 200, max_trx_id = 301(假设下一个分配 ID 是 301)。

现在遇到一条记录,其 row_trx_id 为:
90 → 90 < min_trx_id(200) → 可见(老版本,由更早提交的 Tx 生成)。
320 → 320 >= max_trx_id(301) → 不可见(由之后启动的事务生成)。
200 → 在 [min,max),并且 200 ∈ m_ids → 不可见(当时 T2 还活着)。
205 → 在 [min,max),但 205 ∉ m_ids → 可见(说明 205 的事务在创建 Read View 时已不在活跃列表 —— 已提交)。

跟 Undo Log 的配合(实际步骤)

行上只保存当前版本的 trx_id 和 roll_pointer(指向 undo log 的旧版本)。

当可见性检查判为“当前版本不可见”(例如属于别的活跃事务或未来事务),InnoDB 会沿着 roll_pointer 到 undo log 找前一个版本,对那个版本再走同样的可见性判定,直到找到某个对当前 Read View 可见的版本,或没有更早版本(此时记录视为不存在)。

因此 Read View 的判定和 undo log 的版本链配合起来,完成“回溯到对当前快照可见的历史值”。

  • Copyrights © 2023-2025 Hexo

请我喝杯咖啡吧~

支付宝
微信