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 操作:记录旧数据的完整拷贝(用于构建历史版本)。
版本链的构建流程
以一行数据为例
初始状态
数据行id=1
,值value=A
,事务IDtxid=100
,回滚指针指向NULL
。| id=1 | value=A | DB_TRX_ID=100 | DB_ROLL_PTR=NULL |
事务 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 中的旧版本)|
事务 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
:
如果
DB_TRX_ID < low_limit_id
且不在活跃事务列表中 → 可见(该版本在事务开始时已提交)。如果
DB_TRX_ID >= up_limit_id
→ 不可见(该版本在事务开始后创建)。如果
DB_TRX_ID
在活跃事务列表中 → 不可见(该版本的事务尚未提交)。其他情况需进一步判断(如版本是否被删除)。
不同隔离级别实现
1. 读已提交(Read Committed)
每次查询生成新的 Read View。
能看到其他事务 已提交的最新修改。
2. 可重复读(Repeatable Read)
事务开始时生成一次 Read View,后续查询沿用该视图。
始终看到事务开始时的数据快照,其他事务的修改不可见(解决不可重复读)。
3. 幻读的特殊处理
MySQL 通过 Next-Key Lock(间隙锁+行锁)解决幻读问题。
PostgreSQL 依赖快照隔离,但严格的可串行化隔离级别需要显式锁。
3) 并发冲突检测与解决
常见的并发冲突类型
写-写冲突(Lost Update)
两个事务同时修改同一数据,后提交的事务覆盖前一个事务的结果(如库存扣减场景)。读-写冲突(Dirty Read/Unrepeatable Read)
事务 A 读取数据后,事务 B 修改了该数据,导致事务 A 的后续操作不一致。幻读(Phantom Read)
事务 A 读取某范围数据时,事务 B 插入或删除了符合该范围的数据,导致事务 A 两次读取结果不一致。
冲突解决策略
版本链与回滚
- 当检测到冲突时(如写-写冲突),MVCC通过版本链回溯到可用的旧版本,确保读操作不受影响。
- 例如,InnoDB的
undo log
存储旧版本数据,供回滚和一致性读使用
垃圾回收机制
- 定期清理无效版本(如PostgreSQL的
VACUUM
、MySQL的purge线程
)。 - 通过检查
xmax
状态(已提交或未提交)和事务活跃状态,删除过期版本
- 定期清理无效版本(如PostgreSQL的