MySQL 的多版本并发控制
MySQL 的多版本并发控制(MVCC)详解
1. 什么是 MVCC?
MVCC(Multi-Version Concurrency Control,多版本并发控制) 是 MySQL InnoDB 引擎的一种并发控制机制,通过保存数据的多个版本,允许不同事务读取不同的数据版本,从而减少锁冲突,提高并发性能。
2. MVCC 主要用于哪种隔离级别?
- 支持
REPEATABLE READ
(可重复读)和READ COMMITTED
(读已提交) - 不适用于
READ UNCOMMITTED
(读未提交)和SERIALIZABLE
(可串行化)READ UNCOMMITTED
:直接读最新数据,不需要 MVCC。SERIALIZABLE
:使用锁控制并发,也不需要 MVCC。
3. MVCC 的核心机制
MVCC 主要依赖 undo log
(回滚日志)+ Read View
(读视图) 实现多版本数据管理。
(1)数据版本与 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) |
(2)Read View
(读取视图)
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
。
- 所有新开启事务的
示例:Read View 过滤可见数据
假设有如下数据:
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
修改前的数据。
4. MVCC 解决了哪些并发问题?
✅ 解决了“不可重复读”
不可重复读(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
提交的新数据,避免了“不可重复读”。
❌ MVCC 无法解决幻读
幻读(Phantom Read):
同一事务内,两次
SELECT
,第二次查询时看到新INSERT
的数据。
🔹 MVCC 不能防止 INSERT
造成的幻读
Read View
只能隐藏已修改的数据的新版本,但无法屏蔽新增数据。- 解决方案:使用锁机制(Next-Key Lock)防止
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
插入新数据,从而避免幻读!
5. 总结
机制 | 作用 | 依赖的关键技术 | 适用的隔离级别 |
---|---|---|---|
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 生效) |
🚀 重点总结
- MVCC 依赖
undo log
+Read View
,让事务读取自己的数据版本,减少锁冲突,提高并发性能。 - MVCC 只能解决“不可重复读”问题,但无法解决“幻读”,
INSERT
造成的幻读必须用锁来解决。 REPEATABLE READ
下的Read View
保证了事务期间数据一致性,但新插入的数据仍然可见。SELECT ... FOR UPDATE
触发Next-Key Lock
,防止INSERT
,避免幻读。
这就是 MySQL 的 MVCC 机制,让事务高效并发执行,同时避免大部分锁竞争! 🚀🔥