MySQL中MVCC(多版本并发控制)的实现原理
MySQL MVCC通过Undo Log和隐藏字段构建数据版本链。事务利用Read View(快照)判断版本可见性,从而读取历史数据,实现读不加锁的高并发控制。
MySQL 中 MVCC(Multi-Version Concurrency Control,多版本并发控制)的实现原理是一个核心知识点,它主要用于提高数据库的并发性能,尤其是在处理读操作时,可以做到非阻塞读。
MVCC 的实现主要依赖于以下几个核心要素:
1. 核心要素
MySQL 中实现 MVCC 的主要是 InnoDB 存储引擎,它依赖于行记录中存储的特殊隐藏字段和 undo log。
1.1 隐藏字段(Hidden Columns)
InnoDB 在每行记录中都会添加一些隐藏的、对用户不可见的字段:
| 字段名称 | 作用 |
|---|---|
DB_TRX_ID (事务ID) |
记录最近一次修改(插入或更新)该行记录的事务 ID。这个 ID 是一个递增的数字。 |
DB_ROLL_PTR (回滚指针) |
指向 undo log(回滚日志)中该行记录的上一个版本。通过这个指针,可以构建出该行记录的历史版本链。 |
DB_ROW_ID (隐藏行ID) |
如果表中没有定义主键和非空唯一键,InnoDB 会自动生成一个隐含的自增 ID 作为主键。 |
1.2 Undo Log(回滚日志)
Undo Log 是 MVCC 能够工作的关键。它记录了数据行被修改前的样子。当数据被更新时,旧的数据版本并不会立即删除,而是被记录在 Undo Log 中,并通过 DB_ROLL_PTR 关联起来,形成一个版本链(Version Chain)。
- 作用:
- 回滚 (Rollback): 当事务需要撤销操作时,可以利用 undo log 恢复到修改前的状态。
- MVCC: 提供历史版本数据,供其他并发事务读取。
1.3 Read View(快照读)
Read View(读视图)是 MVCC 实现“一致性读”的核心机制。当事务执行 快照读(Snapshot Read) 操作时(即普通的 SELECT 语句,非锁定读),InnoDB 会为该事务生成一个独立的 Read View。
Read View 包含以下关键信息:
m_ids(活跃事务 ID 列表): 当前系统中所有正在活跃(还未提交或回滚)的事务 ID 列表。min_trx_id(最小事务 ID):m_ids列表中最小的事务 ID。max_trx_id(下一个事务 ID): 预估的下一个待分配的事务 ID(比当前已分配的最大 ID 大 1)。
2. MVCC 的工作流程
当一个事务 T 尝试读取一行记录 R 时,InnoDB 会使用 T 的 Read View 和 R 记录的 DB_TRX_ID 进行对比,以判断该行记录对于事务 T 是否可见。
2.1 版本可见性判断规则
假设当前事务的 Read View 是 RV,待检查记录的事务 ID 是 Trx_ID:
规则 1:如果 Trx_ID < min_trx_id
这意味着该记录在当前所有活跃事务启动之前就已经提交了。
- 结果: 该版本可见。
规则 2:如果 Trx_ID >= max_trx_id
这意味着该记录是由一个在当前事务启动之后才启动的事务修改或插入的。
- 结果: 该版本不可见(必须沿着版本链找旧版本)。
规则 3:如果 min_trx_id <= Trx_ID < max_trx_id
这意味着该记录的修改者是一个在当前事务启动时,系统里“正在运行”的事务。此时需要查看 m_ids 列表:
- 如果
Trx_ID在m_ids列表中: 意味着修改者事务在当前读事务启动时仍活跃且尚未提交。- 结果: 该版本不可见(必须沿着版本链找旧版本)。
- 如果
Trx_ID不在m_ids列表中: 意味着修改者事务在当前读事务启动前已经提交了。- 结果: 该版本可见。
2.2 查找过程(版本链追溯)
如果当前记录版本不可见,事务就会沿着 DB_ROLL_PTR 指针,从 undo log 中取出该行的上一个历史版本,然后重复上述可见性判断规则,直到找到第一个对当前事务可见的历史版本,这就是事务 T 应该看到的数据。
3. Read View 的生成时机与隔离级别
MVCC 的效果和隔离级别密切相关,主要体现在 Read View 的生成时机上:
3.1 Repeatable Read (RR) - 可重复读
在 RR 级别下,一个事务的 Read View 只在事务开始时生成一次。
- 效果: 事务在整个生命周期内都使用这个固定的快照。即使其他事务提交了新的数据,本事务也不会看到,从而保证了“可重复读”的隔离性。这是 MySQL RR 级别能避免幻读的关键。
3.2 Read Committed (RC) - 读已提交
在 RC 级别下,一个事务的 Read View 在每一次执行快照读时都会重新生成。
- 效果: 事务可以看到其他事务在它最近一次查询之前提交的修改。这导致了 不可重复读 的问题(在同个事务内两次查询结果可能不同),但解决了脏读。
4. 总结与局限性
4.1 优点
- 提高并发性: 读操作(快照读)不需要等待写锁释放,实现了非阻塞读。
- 一致性保证: 提供了“时间旅行”的能力,每个事务都看到在它启动时数据库的一致性状态。
4.2 局限性(锁定读)
MVCC 机制仅适用于快照读(Snapshot Read),即普通的 SELECT 语句。
对于需要加锁的当前读(Current Read),例如:
SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEUPDATE,INSERT,DELETE
这些操作必须读取最新的数据版本,并进行加锁,因此它们不能利用 MVCC 的版本链机制,而是遵循传统的锁定机制,可能会阻塞。