数据库的MVCC机制详解

发布于:2025-04-10 ⋅ 阅读:(39) ⋅ 点赞:(0)

MVCC(Multi-Version Concurrency Control,多版本并发控制)是数据库系统中常用的并发控制机制,它允许数据库在同一时间点保存数据的多个版本,从而实现非阻塞的读操作,提高并发性能。

MVCC的核心思想是:

  • 读不阻塞写,写不阻塞读
  • 每个事务看到的是数据库在事务开始时的一致性快照
  • 通过版本链实现数据的多版本存储

为什么需要MVCC?

我们知道数据库有行锁,表锁来保证数据的安全性,但同时也有可能导致阻塞(响应缓慢)和死锁(不可用)。而MVCC正是解决这些问题的机制。MVCC通过保存数据的历史版本来避免读写冲突,这样读操作不会阻塞写操作,写操作也不会阻塞读操作。

MVCC主要功能有哪些?

1) 数据版本管理

2) 事务隔离级别控制 

3) 并发冲突检测与解决

4) 数据一致性维护

1) 数据版本管理

数据库为每一行数据会维护一个版本链,其中有两个主要的信息,事务id与回滚指针。

回滚指针指向的是回滚日志

  • 每次修改数据时(如 INSERT/UPDATE/DELETE),数据库会记录旧数据的 undo log。

  • Undo log 按事务隔离,形成版本链:

    • INSERT 操作:记录插入数据的 undo log(用于回滚时删除)。

    • UPDATE/DELETE 操作:记录旧数据的完整拷贝(用于构建历史版本)。

版本链的构建流程

以一行数据为例

  1. 初始状态
    数据行 id=1,值 value=A,事务ID txid=100,回滚指针指向 NULL

    | id=1 | value=A | DB_TRX_ID=100 | DB_ROLL_PTR=NULL |
  2. 事务 txid=200 更新值

    • 生成新版本数据 value=B,更新 DB_TRX_ID=200

    • 将旧版本数据 value=A 写入 undo log。

    • 新版本的回滚指针指向旧版本的 undo log 地址。

    新版本数据:| id=1 | value=B | DB_TRX_ID=200 | DB_ROLL_PTR=0x123(指向 undo log 中的旧版本)|
  3. 事务 txid=300 再次更新值

    • 生成新版本 value=C,更新 DB_TRX_ID=300

    • 旧版本 value=B 写入 undo log。

    • 版本链形成:C ← B ← A(通过回滚指针链接)。

2) 事务隔离级别控制 

可见性规则(判断数据是否可见)

事务读取数据时,需根据 快照版本 和 事务ID 判断哪个版本对其可见。规则如下:

1. Read View(读视图)

每个事务在首次查询时会生成一个 Read View,包含:

  • 活跃事务列表:当前未提交的事务ID集合。

  • 最小活跃事务ID(low_limit_id):活跃事务中的最小ID。

  • 最大事务ID(up_limit_id):下一个即将分配的事务ID(即当前最大ID+1)。

2. 可见性判断逻辑

对数据行的每个版本,检查其 DB_TRX_ID

  1. 如果 DB_TRX_ID < low_limit_id 且不在活跃事务列表中 → 可见(该版本在事务开始时已提交)。

  2. 如果 DB_TRX_ID >= up_limit_id → 不可见该版本在事务开始后创建)。

  3. 如果 DB_TRX_ID 在活跃事务列表中 → 不可见该版本的事务尚未提交)。

  4. 其他情况需进一步判断(如版本是否被删除)。

不同隔离级别实现
1. 读已提交(Read Committed)
  • 每次查询生成新的 Read View。

  • 能看到其他事务 已提交的最新修改

2. 可重复读(Repeatable Read)
  • 事务开始时生成一次 Read View,后续查询沿用该视图。

  • 始终看到事务开始时的数据快照,其他事务的修改不可见(解决不可重复读)。

3. 幻读的特殊处理
  • MySQL 通过 Next-Key Lock(间隙锁+行锁)解决幻读问题。

  • PostgreSQL 依赖快照隔离,但严格的可串行化隔离级别需要显式锁。

3) 并发冲突检测与解决

常见的并发冲突类型
  1. 写-写冲突(Lost Update)
    两个事务同时修改同一数据,后提交的事务覆盖前一个事务的结果(如库存扣减场景)。

  2. 读-写冲突(Dirty Read/Unrepeatable Read)
    事务 A 读取数据后,事务 B 修改了该数据,导致事务 A 的后续操作不一致。

  3. 幻读(Phantom Read)
    事务 A 读取某范围数据时,事务 B 插入或删除了符合该范围的数据,导致事务 A 两次读取结果不一致。

冲突解决策略
  1. 版本链与回滚

    • 当检测到冲突时(如写-写冲突),MVCC通过版本链回溯到可用的旧版本,确保读操作不受影响。
    • 例如,InnoDB的undo log存储旧版本数据,供回滚和一致性读使用
  2. 垃圾回收机制

    • 定期清理无效版本(如PostgreSQL的VACUUM、MySQL的purge线程)。
    • 通过检查xmax状态(已提交或未提交)和事务活跃状态,删除过期版本