读不加锁的魔法:MySQL 中的 MVCC 到底是什么?
|
🌺The Begin🌺点点关注,收藏不迷路🌺
|
揭开 InnoDB 高并发读写的底层密码
1. 引言:数据库的读写矛盾
在高并发的数据库系统中,存在一个经典矛盾:
| 需求 | 锁方案 | 问题 |
|---|---|---|
| 读多写少 | 读锁 + 写锁互斥 | 读会阻塞写,写会阻塞读,并发度低 |
| 读写并发 | 无锁方案 | 可能读到未提交的脏数据 |

MVCC(Multi-Version Concurrency Control,多版本并发控制) 就是为了解决这个矛盾而生的技术——让读操作不加锁,写操作不阻塞读。
本文将深度剖析 InnoDB 中 MVCC 的实现原理。
2. MVCC 的核心思想
2.1 一句话理解 MVCC
MVCC 的本质是:保存数据的多个历史版本,让不同事务看到不同版本的数据,从而实现读写不互斥。
2.2 MVCC 解决的问题
| 问题 | 传统锁方案 | MVCC 方案 |
|---|---|---|
| 脏读 | 加读锁 | 读已提交版本 |
| 不可重复读 | 加行锁 | 读同一快照 |
| 幻读 | 加间隙锁 | RR级别下用 Read View 抑制幻读 |
| 读写阻塞 | 互斥阻塞 | 完全不阻塞 |
2.3 InnoDB 中的实现范围
InnoDB 的 MVCC 仅实现在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下:
| 隔离级别 | MVCC 行为 |
|---|---|
| READ UNCOMMITTED | 不启动 MVCC,直接读最新值(可能脏读) |
| READ COMMITTED | 每次查询生成新的 Read View(可读到已提交的新数据) |
| REPEATABLE READ | 首次查询生成 Read View,事务内不变(可重复读) |
| SERIALIZABLE | 退化为加锁读,MVCC 辅助 |
3. MVCC 的三大基石
InnoDB MVCC 的实现依赖于三个核心组件:
3.1 隐藏列:数据行自带版本号
InnoDB 为每行数据额外添加了三个隐藏列:
| 隐藏列 | 大小 | 作用 |
|---|---|---|
| DB_TRX_ID | 6 字节 | 最后修改该行的事务 ID |
| DB_ROLL_PTR | 7 字节 | 指向 Undo Log 版本链的指针 |
| DB_ROW_ID | 6 字节 | 行 ID(表无主键时用于聚簇索引) |
3.2 Undo Log:版本链
每次修改数据时,旧版本会写入 Undo Log,并通过 DB_ROLL_PTR 串联成版本链:
三条关键规则:
- INSERT:创建新版本,
DB_TRX_ID为当前事务 ID - UPDATE:将旧数据写入 Undo Log,更新数据行并修改
DB_TRX_ID - DELETE:标记删除(逻辑删除),版本链中保留被删除的版本
3.3 Read View:事务的“快照镜头”
Read View 是事务发起查询时生成的一致性视图,记录了此刻数据库中所有活跃事务的状态。
[101, 105, 108]] -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'SQS'
| 字段 | 含义 |
|---|---|
low_limit_id | 当前未分配的最小事务 ID(新事务的起点) |
up_limit_id | 当前活跃事务中最小的 ID |
active_trx_ids | 当前所有活跃事务 ID 的集合 |
4. 可见性判断算法
当事务读取某行数据时,InnoDB 沿着版本链从新到旧遍历,找到第一个对当前事务可见的版本。
4.1 判断流程图
4.2 判断规则总结
| 条件 | 可见性 | 说明 |
|---|---|---|
trx_id < up_limit_id | ✅ 可见 | 事务在 Read View 生成前已提交 |
trx_id >= low_limit_id | ❌ 不可见 | 事务在 Read View 生成后开启 |
trx_id 在活跃列表中 | ❌ 不可见 | 事务未提交(且非当前事务) |
trx_id = 当前事务ID | ✅ 可见 | 自己的修改当然可见 |
trx_id 不在活跃列表且 < low_limit_id | ✅ 可见 | 已提交的事务 |
5. RC vs RR:Read View 生成时机的差异
这是面试中最常被问到的问题——RC 和 RR 下 MVCC 的唯一区别:
| 隔离级别 | Read View 生成时机 | 效果 |
|---|---|---|
| READ COMMITTED | 每条 SELECT 语句执行时生成新 Read View | 能读到其他事务已提交的新数据 → 不可重复读 |
| REPEATABLE READ | 第一条 SELECT 语句执行时生成,事务内复用 | 同一事务多次读取结果一致 → 可重复读 |
5.1 RC 行为示例
5.2 RR 行为示例
6. MVCC 实战演示
6.1 准备测试数据
-- 创建测试表
CREATE TABLE `account` (
`id` int NOT NULL PRIMARY KEY,
`balance` int DEFAULT 0
) ENGINE=InnoDB;
-- 插入初始数据
INSERT INTO account VALUES (1, 100);
6.2 模拟 MVCC 行为
-- 事务A (开启 RR 隔离级别)
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT balance FROM account WHERE id=1; -- 结果: 100
-- 事务B (开启独立会话)
BEGIN;
UPDATE account SET balance = 200 WHERE id=1;
COMMIT;
-- 事务A 再次查询
SELECT balance FROM account WHERE id=1; -- 结果: 100 (复用 Read View)
-- 事务A 提交后再次查询
COMMIT;
SELECT balance FROM account WHERE id=1; -- 结果: 200 (新事务,新 Read View)
6.3 查看版本链
-- 模拟多次更新制造版本链
BEGIN;
UPDATE account SET balance = 300 WHERE id=1;
COMMIT;
BEGIN;
UPDATE account SET balance = 400 WHERE id=1;
COMMIT;
-- 查看 Undo 信息 (需要特殊工具或开启 monitoring)
-- 在 MySQL 8.0 可以通过 performance_schema 查看
SELECT * FROM performance_schema.data_locks\G
7. MVCC 与锁的协作
很多人误以为 MVCC 完全替代了锁,实则不然——MVCC 仅针对 SELECT 读操作,写操作仍需加锁:
| 操作类型 | 是否使用 MVCC | 锁机制 |
|---|---|---|
| 普通 SELECT(快照读) | ✅ 是 | 不加锁 |
| SELECT … FOR UPDATE | ❌ 否 | 加行锁/间隙锁 |
| SELECT … LOCK IN SHARE MODE | ❌ 否 | 加共享锁 |
| INSERT / UPDATE / DELETE | ❌ 否 | 加排他锁 |
快照读 vs 当前读:
-- 快照读:读取 Read View 中的版本(MVCC)
SELECT * FROM account WHERE id=1;
-- 当前读:读取最新已提交版本(加锁)
SELECT * FROM account WHERE id=1 LOCK IN SHARE MODE;
SELECT * FROM account WHERE id=1 FOR UPDATE;
8. 常见面试题解答
Q1:MVCC 能解决幻读吗?
在 RR 级别下,MVCC + 间隙锁共同解决幻读:
- 快照读(普通 SELECT):通过复用 Read View 抑制幻读
- 当前读(SELECT FOR UPDATE):通过间隙锁锁住范围,阻止插入
Q2:长事务为什么会导致 Undo 膨胀?
因为 Read View 仍持有最早版本,Purge 线程无法清理长事务开始之后生成的 Undo 版本链。
Q3:RR 级别下能读到自己的修改吗?
可以。可见性判断规则中,trx_id = 当前事务ID 时直接可见。
Q4:MySQL 8.0 对 MVCC 有什么优化?
- Undo Log 可独立表空间,避免系统表空间膨胀
- 增加
information_schema.INNODB_TRX等视图方便排查 - 提升清理效率
9. 总结
| 关键点 | 总结 |
|---|---|
| MVCC 是什么 | 多版本并发控制,让读不加锁、读写不互斥 |
| 核心组件 | 隐藏列 + Undo Log 版本链 + Read View |
| 可见性判断 | 基于 Read View 的比较逻辑 |
| RC vs RR | 区别仅在于 Read View 生成时机 |
| 与锁的关系 | MVCC 仅用于快照读,写操作和当前读仍需加锁 |
| 适用隔离级别 | READ COMMITTED 和 REPEATABLE READ |
一句话总结:MVCC 通过保存数据的历史版本,配合 Read View 一致性视图,实现了读写不互斥的并发控制,是 InnoDB 高性能的核心设计之一。
原创不易,欢迎点赞收藏 💡
评论区聊聊:你遇到过 MVCC 相关的什么坑?
标签:MySQL MVCC 多版本并发控制 InnoDB 事务隔离 Read View Undo Log

|
🌺The End🌺点点关注,收藏不迷路🌺
|
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/qq_41840843/article/details/161495758




