
MySQL 的 MVCC (Multi-Version Concurrency Control, 多版本并发控制) 是其核心存储引擎 InnoDB 实现高并发、非阻塞读的关键机制。它允许读操作不阻塞写操作,写操作也不阻塞读操作(在特定隔离级别下),从而极大地提升了数据库的并发性能。
以下是 MySQL InnoDB MVCC 的核心要点和工作原理:
核心思想:保留数据的历史版本
当一行数据被修改时,InnoDB 不会立即覆盖原始数据,而是将原始数据标记为旧版本,并创建一个新版本。
同时进行的读操作仍然可以访问到修改发生前的旧版本数据(快照)。
数据的多个版本(旧版本)保存在 Undo Log 段中。
实现基础:隐藏字段
InnoDB 在每一行记录(聚簇索引的叶子节点)中,除了用户定义的列,还添加了三个隐藏的系统字段:DB_TRX_ID
(6 Bytes): 记录最后一次修改(INSERT 或 UPDATE)该行记录的事务 ID。DB_ROLL_PTR
(7 Bytes): 指向该行记录上一个版本在 Undo Log 中位置的指针(回滚指针)。利用这个指针可以构建一条该行记录的版本链。DB_ROW_ID
(6 Bytes): 隐含的自增行 ID(Row ID)。如果表没有定义主键,InnoDB 会自动生成一个聚簇索引基于此字段。关键组件:Undo Log
事务回滚 (
ROLLBACK
):通过DB_ROLL_PTR
找到旧版本数据并恢复。实现 MVCC:提供一致性读所需的历史快照。
UPDATE
: 将修改前的行数据(包含所有字段)复制到 Undo Log 中,形成一个旧版本记录。然后更新当前行的数据,并将DB_TRX_ID
设置为当前事务 ID,DB_ROLL_PTR
指向刚刚写入 Undo Log 的旧版本记录。DELETE
: InnoDB 实际上并不立即删除数据,而是将其标记为删除(设置一个删除标志)。同时,将删除前的整行数据复制到 Undo Log 中。真正的物理删除(Purge)由后台线程在确定没有任何事务需要访问这些旧版本数据时才进行。存储被修改数据的旧版本。
当进行
UPDATE
或DELETE
操作时:Undo Log 主要用于:
可见性判断:Read View
如果
DB_TRX_ID
在trx_ids
列表中,表示该事务当时活跃(未提交),则该版本不可见,继续找旧版本。如果
DB_TRX_ID
不在trx_ids
列表中,表示该事务在生成 Read View 时已提交,则该版本可见。trx_ids
: 生成 Read View 时,系统内所有活跃(已启动但未提交)的读写事务的事务 ID 列表。low_limit_id
: 生成 Read View 时,系统应该分配给下一个事务的 ID 值(即当前最大事务 ID + 1)。up_limit_id
: 活跃事务列表trx_ids
中最小的事务 ID。creator_trx_id
: 创建该 Read View 的事务自身的事务 ID(对于只读事务,该值为 0)。当一个事务(通常是只读事务或开启了
START TRANSACTION WITH CONSISTENT SNAPSHOT
)执行普通的SELECT
查询(快照读)时,InnoDB 会为该事务生成一个 Read View(读视图)。Read View 决定了该事务能看到数据的哪个版本。它包含以下关键信息:
可见性规则:对于版本链中的每一行记录(通过
DB_ROLL_PTR
回溯),使用该行的DB_TRX_ID
和当前事务的 Read View 进行判断:事务会沿着版本链(通过
DB_ROLL_PTR
)依次应用这些规则,直到找到第一个对其可见的版本。如果
DB_TRX_ID
<up_limit_id
:说明该版本是在生成 Read View 之前就已提交的事务修改的,可见。如果
DB_TRX_ID
>=low_limit_id
:说明该版本是在生成 Read View 之后才开启的事务修改的(或者就是本事务修改的但未提交),不可见。需要沿着版本链继续查找更旧的版本。如果
up_limit_id
<=DB_TRX_ID
<low_limit_id
:说明该版本是由在生成 Read View 时活跃的事务修改的。如果
DB_TRX_ID
=creator_trx_id
:说明该版本是由本事务自己修改的,可见。与事务隔离级别的关系
MVCC 主要在 READ COMMITTED (RC) 和 REPEATABLE READ (RR) 隔离级别下发挥核心作用。它们的主要区别在于 Read View 的生成时机:在事务中第一次执行
SELECT
语句(快照读)时生成一个 Read View。在整个事务期间,后续的所有快照读都复用这个第一次生成的 Read View。因此,在同一个事务内,多次读取同一条记录,看到的是同一个历史快照(第一次读时的状态)。解决了脏读和不可重复读。
InnoDB 在 RR 级别下还通过 Next-Key Locking(间隙锁)机制来防止幻读(MVCC 的快照读本身不能完全防止幻读,因为新插入的数据可能不在快照中,但当前读会加锁阻止插入)。
每次执行
SELECT
语句(快照读)时,都会重新生成一个最新的 Read View。因此,它能读取到最新已提交的数据。解决了脏读,但可能出现不可重复读和幻读。
READ COMMITTED (RC):
REPEATABLE READ (RR):
快照读 vs. 当前读
SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEUPDATE
,DELETE
,INSERT读取的是数据的最新提交版本。
会加锁(记录锁、间隙锁、Next-Key 锁)以保证操作的正确性和防止并发冲突。
目的是处理当前最新的数据状态并进行修改。
普通的
SELECT
语句(不加FOR UPDATE
或LOCK IN SHARE MODE
)。使用 MVCC 机制,读取历史版本(Read View 决定),不加锁(理论上,可能遇到历史数据被 Purge 清理的情况,但通常不影响一致性)。
目的是提供一致性的非锁定读。
快照读 (Snapshot Read):
当前读 (Current Read):
Purge 操作
随着事务的提交,不再需要访问的旧版本数据(Undo Log)会变得多余。
InnoDB 的后台线程 Purge Thread 会负责清理这些不再被任何 Read View 依赖的旧版本数据和 Undo Log 空间。这个过程称为 Purge。
长事务会阻止 Purge 线程清理它可能需要的旧版本数据,导致 Undo Log 膨胀,这是需要注意的。
总结 MySQL InnoDB MVCC 的优势:
高并发读: 读操作通常不需要加锁,不会被写操作阻塞。
非阻塞写: 写操作通常只需要锁定当前处理的行(或间隙),不会阻塞其他行的读操作(快照读)。
一致性读: 在不同隔离级别(RC/RR)下提供不同强度的一致性视图,避免脏读,解决不可重复读(RR)。
高效回滚: Undo Log 天然支持事务回滚。
需要注意:
MVCC 主要在 READ COMMITTED 和 REPEATABLE READ 隔离级别下有效。READ UNCOMMITTED 总是读最新数据(可能脏读),SERIALIZABLE 会对所有读加锁,不使用 MVCC 的快照读。
MVCC 会增加存储开销(Undo Log 存储历史版本)和 CPU 开销(构建和遍历版本链)。
理解
DB_TRX_ID
,DB_ROLL_PTR
, Undo Log, Read View 以及可见性规则是掌握 MVCC 的关键。
通过 MVCC,MySQL InnoDB 在保证事务隔离性的同时,极大地提高了数据库的并发处理能力,是现代 OLTP 应用高性能的重要基石。




