深入解析:数据库MVCC

MVCC

一、MVCC 是什么?

MVCC 的全称是 Multi-Version Concurrency Control,即多版本并发控制

它是一种数据库管理技巧,用于提高数据库在高并发场景下的性能。MVCC 通过在同一时刻保留数据行的多个版本,使得读操作(SELECT)不会阻塞写操作(UPDATE, DELETE),写操作也不会阻塞读操作。它完美解决了读写之间不必要的阻塞挑战,极大地提升了并发能力。

MySQL 中InnoDB存储引擎的核心特性之一就是实现了 MVCC。

二、为什么应该 MVCC?—— 克服读写冲突

在没有 MVCC 的情况下,数据库通常通过来实现事务的隔离性。

  • 如果使用写锁(排他锁,X Lock),一个事务在写数据时,会阻塞其他事务的读和写。
  • 如果使用读锁(共享锁,S Lock),一个事务在读数据时,会阻塞其他事务的写,但允许其他事务读。

这种“锁”的方式虽然能保证数据的一致性,但并发性能很差,因为读和写是互斥的。

MVCC 给出了一种非锁的读(Consistent Nonlocking Read)方式。它让读操作去读一个快照版本,而写操作去创建一个新的版本通过。这样读和写操作的对象是不同的数据版本,因此能够并发执行,互不阻塞。

三、MVCC 的核心工作原理

MVCC 的实现依赖于三个核心概念一个关键机制

  1. 三个隐藏字段
  2. Undo Log(回滚日志)
  3. Read View(读视图)
1. 三个隐藏字段

InnoDB 为每一行材料都额外添加了三个用户看不到的隐藏字段:

  • DB_TRX_ID (6字节):最后修改该数据行的事务ID。记录是哪个事务插入或修改了这行数据。
  • DB_ROLL_PTR (7字节):回滚指针。指向该行数据在 Undo Log 中的上一个历史版本。它将所有版本的数据串联成一个版本链
  • DB_ROW_ID (6字节):行标识(隐藏主键)。若是表没有定义主键,InnoDB 会自动生成这个字段作为聚簇索引的键。
2. Undo Log (回滚日志)

Undo Log 保存了数据被修改前的旧版本数据。当执行 UPDATEDELETE 操作时,旧版本的数据并不会被立刻删除或覆盖,而是会被拷贝到 Undo Log 中。

  • 每修改一次,就会在 Undo Log 中生成一条记录。
  • 通过 DB_ROLL_PTR 回滚指针,可以将当前记录的所有历史版本串联起来,形成一个版本链。链头是最新的记录,链尾是最老的记录。
3. Read View (读视图) - MVCC 的“快照”灵魂

Read View事务在进行快照读(普通SELECT)时产生的,它定义了当前事务能看到哪个版本的数据。

Read View 本质上是一个数据结构,主要包含以下关键信息:

  • m_ids:生成 Read View 时,系统中活跃的(未提交的)读写事务ID的集合
  • min_trx_idm_ids 集合中的最小值。
  • max_trx_id:生成 Read View 时,系统应该分配给下一个事务的ID
  • creator_trx_id:创建这个 Read View当前事务的ID

数据可见性规则:
否对当前事务可见。判断规则如下:就是当访问某一行数据时,MVCC 会从最新的版本开始,顺着版本链依次判断每个版本

  1. 如果被访问版本的 DB_TRX_ID小于min_trx_id,说明该版本在 Read View 创建前就已提交,对当前事务可见
  2. 如果被访问版本的 DB_TRX_ID大于等于max_trx_id,说明该版本在 Read View 创建后才生成,对当前事务不可见。得顺着版本链继续找更老的版本。
  3. 如果被访问版本的 DB_TRX_IDmin_trx_idmax_trx_id 之间(min_trx_id <= trx_id < max_trx_id):
    • DB_TRX_IDm_ids(活跃事务集合),说明创建该版本的事务当时还未提交,该版本不可见
    • DB_TRX_ID不在m_ids,说明创建该版本的事务当时已经提交,该版本可见
  4. 如果当前记录版本的 DB_TRX_ID 等于 creator_trx_id,说明是这个事务自己修改的记录,对自己始终可见的

一旦找到第一个对当前事务可见的版本,就返回这个版本的材料。

简单解释: Read View 就是 InnoDB 给每个快照读开的“时间戳发票”

事务拿到这张发票后,整个事务期间都按这张发票上的规则,判断到底能看哪个“历史版本”的行记录。

发票一旦打印,内容就不会再变,所以 repeatable read 才能做到“可重复”。

1.发票长什么样(4 个字段)

字段名含义类比
m_ids开票瞬间,还没提交的所有事务编号黑名单
min_trx_id黑名单里最小的那种编号最早“坏人”
max_trx_id系统下一个要分配的事务编号“未来人”起点
creator_trx_id开票人自己的事务编号我自己
  1. 拿到发票后怎么“验货”

对每条记录的每个版本(顺着 undo 链从头走到尾):

  1. 版本太老DB_TRX_ID < min_trx_id
    → 坏人名单里都没它,说明早就提交了,可见
  2. 版本太新DB_TRX_ID ≥ max_trx_id
    → 这是我开票之后才冒出来的,不可见,继续往 older 版本找。
  3. 版本在中间min_trx_id ≤ DB_TRX_ID < max_trx_id
    • 如果编号在黑名单 m_ids 里 → 开票时它还没提交,不可见
    • 如果编号不在黑名单 → 开票时它已提交,可见
  4. 版本是我自己改的DB_TRX_ID == creator_trx_id
    → 自己写的东西当然能看到,可见

一旦找到第一个“可见”版本就停下来返回,后面的 older 版本不再看。

  1. 一张图秒懂
时间轴:  ...[min_trx_id) ...[m_ids 黑名单]... [max_trx_id)...
           ↑               ↑               ↑
         太老,可见      在黑名单→不可见   太新,不可见
        不在黑名单→可见
  1. 举个数字例子
  • 当前系统里活着的事务:88,90,93
  • Read View:就是于
    m_ids = {88,90,93}
    min_trx_id = 88
    max_trx_id = 95(系统下一个号)
    creator_trx_id = 91(我自己)

来一条记录版本链:

版本号(DB_TRX_ID)判断可见?
96≥ max_trx_id
9488≤94<95 且 94∉m_ids可见 (返回)
  1. 一句话总结

Read View 把“并发世界”瞬间拍成一张静态照片,之后事务无论读多少次,都只看这张照片允许的“历史镜像”,从而

  • 挡住未提交的脏数据(脏读)
  • 挡住已提交的后续改动(不可重复读)
  • 配合间隙锁还能挡住新插入的幻影行(幻读)

这就是 MVCC 里“快照”真正的灵魂。

四、MVCC 如何实现不同隔离级别?

MVCC 核心作用于READ COMMITTED (RC,提交读)REPEATABLE READ (RR,可重复读)这两个隔离级别。

  • READ COMMITTED (RC)
    • 核心:每次执行快照读(SELECT)时,都会生成一个新的 Read View
    • 效果:每次读都能看到最新已经提交的事务所做的修改。所以会出现“不可重复读”现象(同一个事务内两次读取同一数据,结果可能不同)。
  • REPEATABLE READ (RR)
    • 核心:只在第一次执行快照读时生成一个 Read View,后续所有的读运行都复用该 Read View
    • 效果:在整个事务期间,每次读到的数据都是一致的,就像是在事务开始时拍了一个快照一样。因此消除了“不可重复读”问题。(这也是 InnoDB 在 RR 级别下能防止幻读的手段之一)。

五、一个简单的例子

假设:

  • 事务A (id=10) 开启,查询一条记录。
  • 事务B (id=20) 修改了这条记录并提交。
  • 事务A 再次查询。

在 RC 级别下:

  1. 事务A第一次查询,生成 Read View1m_ids 包含 [10](假设只有自己活跃)。它读到的是原始版本。
  2. 事务B修改并提交。
  3. 事务A第二次查询,生成一个新的 Read View2,此时 m_ids 只包含 [10](20已提交)。根据规则,它能看到事务B提交的版本。所以两次查询结果不同(不可重复读)。

在 RR 级别下:

  1. 事务A第一次查询,生成 Read View1m_ids 包含 [10]。
  2. 事务B修改并提交。
  3. 事务A第二次查询,复用之前的 Read View1。根据规则,Read View1 生成时,事务B (20) 还未开始或处于活跃状态(取决于时机),所以事务B修改的版本对 Read View1 不可见。事务A只能看到和第一次一样的原始版本。所以两次查询结果相同(可重复读)。

六、总结

特性说明
目的提高并发性能,实现读写不阻塞
实现基础隐藏字段 (DB_TRX_ID, DB_ROLL_PTR) + Undo Log(版本链) +Read View(可见性判断)。
核心思想为每个事务提供一个数据快照,读操作读历史版本,写操作创建新版本。
适用操作快照读(普通 SELECT ...不加锁)。当前读SELECT ... FOR UPDATE, UPDATE, DELETE, INSERT会加锁)不适用。
与隔离级别关系RCRR隔离级别完成的基础。RC 每次读生成新 Read ViewRR 第一次读生成 Read View 并复用。
优点读不加锁,读写不冲突,并发性能高。
缺点需要维护多版本数据,会占用更多的存储空间;需要复杂的垃圾回收机制来清理不再需要的旧版本数据。

轻松来说,MVCC 就是借助给数据行“拍快照”现代数据库完成高并发的重要技术。就是的方式,让每个事务都能看到一份一致的数据视图,从而巧妙地避免了不必要的锁竞争,

posted @ 2025-10-12 16:57  ycfenxi  阅读(11)  评论(0)    收藏  举报