MVCC 是一种并发控制的方法,主要用于数据库管理系统,允许多个事务同时读取数据库中的同一个数据项,而不需要加锁,从而提高了数据库的并发性能。
┌─────────────────────────────────────┐
│ MVCC 的核心思想 │
│ │
│ 对数据进行修改操作时,不会直接覆盖数据,│
│ 而是创建一个新版本,让读操作可以看到 │
│ 修改前的数据 │
└─────────────────────────────────────┘
InnoDB 引擎下 MVCC 的实现主要基于以下几个关键概念:
InnoDB 为每一行记录添加了三个隐藏字段:
┌───────────────────────────────────────────────────────┐
│ InnoDB 行记录结构 │
├───────────┬───────────┬───────────┬───────────────────┤
│ DB_TRX_ID │ DB_ROLL_PTR│ DB_ROW_ID │ 实际数据列(可见部分) │
│ 事务ID │ 回滚指针 │ 行ID(可选) │ │
└───────────┴───────────┴───────────┴───────────────────┘
当一行记录被修改时,InnoDB 会将旧版本的记录写入 undo log,并在当前记录中通过回滚指针指向这个 undo log 记录,形成一个版本链。
┌──────────────────────────────────────────────────────────────┐
│ 版本链示意图 │
│ │
│ 最新记录 │
│ ┌─────────┬─────────┬─────────┬───────────┐ │
│ │TRX_ID=30│ROLL_PTR │ROW_ID │name="张三" │ │
│ └─────────┴─────────┴─────────┴───────────┘ │
│ │ │
│ ▼ 回滚指针指向 │
│ Undo Log 1 │
│ ┌─────────┬─────────┬─────────┬───────────┐ │
│ │TRX_ID=20│ROLL_PTR │ROW_ID │name="李四" │ │
│ └─────────┴─────────┴─────────┴───────────┘ │
│ │ │
│ ▼ 回滚指针指向 │
│ Undo Log 2 │
│ ┌─────────┬─────────┬─────────┬───────────┐ │
│ │TRX_ID=10│ROLL_PTR │ROW_ID │name="王五" │ │
│ └─────────┴─────────┴─────────┴───────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
ReadView 是 MVCC 实现的关键机制,它决定了当前事务能够看到哪个版本的数据。ReadView 包含以下重要信息:
┌──────────────────────────────────────────────┐
│ ReadView 示意图 │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ m_ids: [10, 20, 30] │ │
│ │ min_trx_id: 10 │ │
│ │ max_trx_id: 40 │ │
│ │ creator_trx_id: 25 │ │
│ └────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────┘
当一个事务要读取一行记录时,它会根据 ReadView 和记录的 DB_TRX_ID 来判断该版本的记录是否可见:
┌─────────────────────────────────────────────────────────────────┐
│ MVCC 可见性判断流程图 │
│ │
│ ┌───────────────┐ │
│ │ 开始判断可见性 │ │
│ └───────┬───────┘ │
│ ▼ │
│ ┌───────────────────────────────┐ 是 ┌───────────┐ │
│ │ trx_id == creator_trx_id? ├────────────►│ 可见 │ │
│ └───────────┬───────────────────┘ └───────────┘ │
│ │ 否 │
│ ▼ │
│ ┌───────────────────────────────┐ 是 ┌───────────┐ │
│ │ trx_id < min_trx_id? ├────────────►│ 可见 │ │
│ └───────────┬───────────────────┘ └───────────┘ │
│ │ 否 │
│ ▼ │
│ ┌───────────────────────────────┐ 是 ┌───────────┐ │
│ │ trx_id >= max_trx_id? ├────────────►│ 不可见 │ │
│ └───────────┬───────────────────┘ └───────────┘ │
│ │ 否 │
│ ▼ │
│ ┌───────────────────────────────┐ 是 ┌───────────┐ │
│ │ trx_id 在 m_ids 中? ├────────────►│ 不可见 │ │
│ └───────────┬───────────────────┘ └───────────┘ │
│ │ 否 │
│ ▼ │
│ ┌───────────┐ │
│ │ 可见 │ │
│ └───────────┘ │
└─────────────────────────────────────────────────────────────────┘
MVCC 主要在 READ COMMITTED 和 REPEATABLE READ 隔离级别下工作:
┌─────────────────────────────────────────────────────────────────┐
│ 不同隔离级别的 ReadView 创建时机 │
│ │
│ ┌────────────────────┐ ┌─────────────────────────────┐ │
│ │ READ COMMITTED │ │ REPEATABLE READ │ │
│ ├────────────────────┤ ├─────────────────────────────┤ │
│ │ │ │ │ │
│ │ 每次SELECT时创建新的 │ │ 事务开始时创建一次ReadView │ │
│ │ ReadView │ │ 之后所有查询复用这个ReadView │ │
│ │ │ │ │ │
│ └────────────────────┘ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
假设我们有一个简单的表格和以下操作序列:
创建表: CREATE TABLE user(id INT PRIMARY KEY, name VARCHAR(20));
初始数据: INSERT INTO user VALUES(1, '小明');
接下来,有三个事务同时操作这条记录:
┌─────────────────────────────────────────────────────────────────┐
│ 事务并发执行示例 │
│ │
│ 时间 │ 事务A(trx_id=10) │ 事务B(trx_id=20) │ 事务C(trx_id=30) │
│ ─────┼──────────────────┼──────────────────┼────────────────── │
│ t1 │ BEGIN; │ │ │
│ t2 │ │ BEGIN; │ │
│ t3 │ │ │ BEGIN; │
│ t4 │ SELECT * FROM │ │ │
│ │ user WHERE id=1; │ │ │
│ │ 结果: '小明' │ │ │
│ t5 │ │ UPDATE user SET │ │
│ │ │ name='小红' │ │
│ │ │ WHERE id=1; │ │
│ t6 │ │ COMMIT; │ │
│ t7 │ SELECT * FROM │ │ │
│ │ user WHERE id=1; │ │ │
│ │ 结果(RC): '小红' │ │ │
│ │ 结果(RR): '小明' │ │ │
│ t8 │ │ │ UPDATE user SET │
│ │ │ │ name='小黑' │
│ │ │ │ WHERE id=1; │
│ t9 │ │ │ COMMIT; │
│ t10 │ SELECT * FROM │ │ │
│ │ user WHERE id=1; │ │ │
│ │ 结果(RC): '小黑' │ │ │
│ │ 结果(RR): '小明' │ │ │
│ t11 │ COMMIT; │ │ │
└─────────────────────────────────────────────────────────────────┘
初始状态:
┌─────────┬─────────┬─────────┬───────────┐
│TRX_ID=1 │ROLL_PTR │ROW_ID │name="小明" │
└─────────┴─────────┴─────────┴───────────┘
事务B更新后:
┌─────────┬─────────┬─────────┬───────────┐
│TRX_ID=20│ROLL_PTR │ROW_ID │name="小红" │
└─────────┴─────────┴─────────┴───────────┘
│
▼
┌─────────┬─────────┬─────────┬───────────┐
│TRX_ID=1 │ROLL_PTR │ROW_ID │name="小明" │
└─────────┴─────────┴─────────┴───────────┘
事务C更新后:
┌─────────┬─────────┬─────────┬───────────┐
│TRX_ID=30│ROLL_PTR │ROW_ID │name="小黑" │
└─────────┴─────────┴─────────┴───────────┘
│
▼
┌─────────┬─────────┬─────────┬───────────┐
│TRX_ID=20│ROLL_PTR │ROW_ID │name="小红" │
└─────────┴─────────┴─────────┴───────────┘
│
▼
┌─────────┬─────────┬─────────┬───────────┐
│TRX_ID=1 │ROLL_PTR │ROW_ID │name="小明" │
└─────────┴─────────┴─────────┴───────────┘
MVCC 是 MySQL InnoDB 存储引擎中实现高并发的关键技术,通过在每行记录后面保存两个隐藏的列(事务ID和回滚指针)来实现的。它能够让不同事务的读、写操作并发执行,同时保证事务的隔离性。根据不同的隔离级别,MySQL 会采用不同的策略来创建和维护 ReadView,从而影响数据的可见性。