MVCC,即 Multiversion Concurrency Control(多版本并发控制),它是数据库实现并发控制的一种方式。
MVCC 的核心思想是:
为每个事务提供数据的“快照”版本,从而避免加锁,提高读操作的并发性。
它通过以下两个隐藏字段实现版本控制:
trx_id
:每行记录最后一次修改它的事务ID。roll_pointer
:指向旧版本(undo log)的指针,形成多版本链。此外,事务开始时有一个 read view(快照视图),用来判断哪些版本是“可见的”。
这种读取叫做 一致性读(Consistent Read),完全不加锁!
MyISAM 不支持事务,因此也就不支持:
当有一个查询时,它只能使用表级锁来保证一致性。这种方式:
MySQL 存储引擎 MVCC 差异总结
事务隔离级别从低到高:
隔离级别 | 描述 | 常见问题 |
---|---|---|
READ UNCOMMITTED | 可以读到未提交的数据 | 脏读(读未提交的数据) |
READ COMMITTED | 只能读到已提交的数据 | 不可重复读 |
REPEATABLE READ | 多次读取同一数据结果一致 | 幻读 |
SERIALIZABLE | 全部加锁,串行执行 | 性能差但最安全 |
多次
SELECT
相同数据时,读到的是事务开始时的数据快照(read view),不受其他事务影响。
SELECT
查询都是基于这个 read view。幻读
,即读取到的不是最新数据,因为可重复读采用的是undo log的read view快照机制,用的是事务开始保存的快照,而不是实时数据。-- 事务 A
START TRANSACTION;
SELECT * FROM user WHERE id=1; -- 假设 name='Tom'
-- 事务 B
START TRANSACTION;
UPDATE user SET name='Jerry' WHERE id=1;
COMMIT;
-- 回到事务 A
SELECT * FROM user WHERE id=1; -- name 仍然是 'Tom',实现了 repeatable read
事务 A 的所有读取都基于它开始时的快照,看到的是“旧世界”。
每次
SELECT
都读取当前最新提交版本的数据。
不可重复读
,即,同一事务中select的是不同数据。-- 事务 A
START TRANSACTION;
SELECT * FROM user WHERE id=1; -- name = 'Tom'
-- 事务 B
START TRANSACTION;
UPDATE user SET name='Jerry' WHERE id=1;
COMMIT;
-- 回到事务 A
SELECT * FROM user WHERE id=1; -- name = 'Jerry'(读到了新数据)
这种机制虽然不会脏读,但不能重复读,因为两次查询结果不一样。
InnoDB 实现事务隔离性核心在于:MVCC + undo log + read view
REPEATABLE READ:
READ COMMITTED:
MySQL 默认使用 REPEATABLE READ,避免幻读靠间隙锁
(gap lock)
隔离级别 | 是否可读未提交 | 是否可重复读 | 是否会幻读 | 使用场景(读一致性) |
---|---|---|---|---|
READ UNCOMMITTED | ✅ 是 | ❌ 否 | ✅ 是 | 最低一致性,无任何保障 |
READ COMMITTED | ❌ 否(读提交) | ❌ 否 | ✅ 是 | 大多数数据库默认,如Oracle |
REPEATABLE READ | ❌ 否 | ✅ 是 | ❌(InnoDB中) | MySQL默认,支持一致快照 |
SERIALIZABLE | ❌ 否 | ✅ 是 | ✅ 否(加锁) | 串行执行,开销大 |
问题类型 | 描述 | 发生条件 |
---|---|---|
脏读(Dirty Read) | 读到了未提交的数据 | 仅在 READ UNCOMMITTED 下可能发生 |
不可重复读(Non-repeatable Read) | 同一条记录两次读结果不一致 | READ COMMITTED |
幻读(Phantom Read) | 两次读取结果行数不一致(新增/删除) | REPEATABLE READ 但有范围查询时才会发生 |
每条记录维护:
- 修改前的值(旧版本)
- 事务 ID(trx_id)
- 回滚指针(roll_pointer)指向上一个版本
当前事务ID = T
记录版本的 trx_id = R
如果 R < 最小活跃事务ID:可见(已经提交)
如果 R == 当前事务ID:可见(自己改的)
如果 R 是活跃事务ID之一:不可见(别人还没提交)
指一类特殊的不可重复读 —— 两次范围查询返回不同数量的结果。
-- 事务 A
START TRANSACTION;
SELECT * FROM user WHERE age > 20;
-- 事务 B
INSERT INTO user (name, age) VALUES ('NewUser', 21);
COMMIT;
-- 回到事务 A
SELECT * FROM user WHERE age > 20; -- 发现多了一条,产生幻读
在可重复读隔离级别下,为防止幻读,InnoDB 对范围查询加“间隙锁”。
SELECT * FROM user WHERE age > 20 FOR UPDATE;
-- 会锁住 20 ~ ∞ 之间的“空隙”,禁止插入
FOR UPDATE
时,一般是一致性读,只靠 undo log,不加锁事务隔离级别问题对比
InnoDB 实现关键:MVCC
https://github.com/0voice