MVCC(Multi-Version Concurrency Control,多版本并发控制) 是 MySQL InnoDB 引擎的一种并发控制机制,通过保存数据的多个版本,允许不同事务读取不同的数据版本,从而减少锁冲突,提高并发性能。
REPEATABLE READ
(可重复读)和 READ COMMITTED
(读已提交)READ UNCOMMITTED
(读未提交)和 SERIALIZABLE
(可串行化)
READ UNCOMMITTED
:直接读最新数据,不需要 MVCC。SERIALIZABLE
:使用锁控制并发,也不需要 MVCC。MVCC 主要依赖 undo log
(回滚日志)+ Read View
(读视图) 实现多版本数据管理。
undo log
undo log
。Read View
规则,从 undo log
获取符合可见性的旧版本。id |
name |
trx_id (修改它的事务) |
undo log 旧值 |
---|---|---|---|
1 | “Tom” | 50 | “Tom_old”(trx_id=48) |
2 | “Alice” | 51 | “Alice_old”(trx_id=49) |
Read View
(读取视图)每个事务第一次执行 SELECT
查询时,会生成一个 Read View
,用来确定哪些事务的更改对当前事务可见,哪些事务的更改不可见。
Read View 主要包含以下信息:
m_ids
(未提交事务 ID 集合)
trx_id
。trx_id
在 m_ids
里,表示该事务还未提交,这条数据对当前事务不可见。low_limit_id
(最小未提交事务 ID)
m_ids
事务中的最小 trx_id
。low_limit_id
的 trx_id
,代表已经提交,对当前事务可见。up_limit_id
(当前最大事务 ID)
trx_id
一定大于 up_limit_id
。假设有如下数据:
id |
name |
trx_id |
---|---|---|
1 | “Tom” | 48 |
2 | “Alice” | 49 |
3 | “Bob” | 50(未提交) |
如果事务 T1
生成 Read View:
m_ids = {50}
(事务 50 还未提交)low_limit_id = 50
up_limit_id = 52
(当前最大 trx_id
)那么,事务 T1
读取时:
trx_id = 48、49
的数据可见(因为 trx_id < low_limit_id
)。trx_id = 50
的数据不可见(因为 trx_id ∈ m_ids
)。Bob
(trx_id = 50
)修改了数据,T1 仍然读取 undo log
中 Bob
修改前的数据。不可重复读(Non-Repeatable Read):
同一事务内,两次
SELECT
结果不一致,因为另一事务UPDATE
了数据并提交。
MVCC 解决方案
REPEATABLE READ
级别下,每个事务的 Read View
在 SELECT
第一次执行时创建,之后查询始终基于这个 Read View
,因此 不会看到其他事务 UPDATE
过的数据。示例
-- 事务 T1
START TRANSACTION;
SELECT name FROM employees WHERE id = 1; -- 读取 "Tom"
-- 事务 T2
UPDATE employees SET name = "Jack" WHERE id = 1;
COMMIT;
-- 事务 T1
SELECT name FROM employees WHERE id = 1; -- 仍然读取 "Tom"
T1 读取的始终是事务开始时的数据版本,不会看到 T2
提交的新数据,避免了“不可重复读”。
幻读(Phantom Read):
同一事务内,两次
SELECT
,第二次查询时看到新INSERT
的数据。
MVCC 不能防止 INSERT
造成的幻读
Read View
只能隐藏已修改的数据的新版本,但无法屏蔽新增数据。INSERT
。示例
-- 事务 T1
START TRANSACTION;
SELECT * FROM employees WHERE salary > 5000; -- 读出 3 条数据
-- 事务 T2
INSERT INTO employees (id, name, salary) VALUES (100, 'Alice', 6000);
COMMIT;
-- 事务 T1
SELECT * FROM employees WHERE salary > 5000; -- 读出 4 条数据(幻读!)
解决方案
SELECT * FROM employees WHERE salary > 5000 FOR UPDATE;
✅ FOR UPDATE
触发 Gap Lock
,防止 T2
插入新数据,从而避免幻读!
机制 | 作用 | 依赖的关键技术 | 适用的隔离级别 |
---|---|---|---|
MVCC | 让不同事务读取各自可见的数据版本,减少锁竞争 | undo log + Read View |
READ COMMITTED 、REPEATABLE READ |
Read View | 确定哪些数据版本对当前事务可见 | trx_id 事务 ID 过滤数据 |
READ COMMITTED 、REPEATABLE READ |
undo log | 存储旧数据版本,支持事务回溯 | 事务修改时记录旧值 | READ COMMITTED 、REPEATABLE READ |
Next-Key Lock | 防止 INSERT 造成幻读 |
行锁 + 间隙锁(Gap Lock) | REPEATABLE READ (仅 FOR UPDATE / SERIALIZABLE 生效) |
undo log
+ Read View
,让事务读取自己的数据版本,减少锁冲突,提高并发性能。INSERT
造成的幻读必须用锁来解决。REPEATABLE READ
下的 Read View
保证了事务期间数据一致性,但新插入的数据仍然可见。SELECT ... FOR UPDATE
触发 Next-Key Lock
,防止 INSERT
,避免幻读。这就是 MySQL 的 MVCC 机制,让事务高效并发执行,同时避免大部分锁竞争!