往期博客
深入理解 MySQL 索引:从 B+ 树到索引下推 深入理解 MySQL 锁:从全局锁到死锁检测
在没有 MVCC 的远古时代,数据库为了保证数据一致性,只能依靠锁机制:读读不互斥,但读写、写写都会互斥。这意味着,如果一张 1.6 亿行的大表正在进行报表查询,所有的写入操作都会被阻塞——这在高并发系统中是不可接受的。
MVCC(多版本并发控制)的出现,就是为了实现"读写不互斥"。 核心思想很简单:你改你的,我读我的历史版本。
MVCC 的两大幕后功臣
要实现"读不阻塞写",MySQL 并不真的去复制整张表,而是巧妙地利用了两个机制:隐藏字段和 Undo Log。
隐藏字段:每行数据的身份牌
当你创建一张表时,InnoDB 会偷偷给每一行加上几个隐藏列:
| DB_TRX_ID | 最近一次修改该行的事务 ID |
| DB_ROLL_PTR | 回滚指针,指向该行的"前世"(存在 Undo Log 中) |
| DB_ROW_ID | 隐藏主键(仅当表没有定义主键时存在) |
Undo Log:数据的后悔药与时光机
每当你更新一行数据,InnoDB 的处理流程是:
这样,一连串的修改就形成了一条版本链。通过这条链,任何事务都可以追溯到它"应该看到"的那个历史版本。
Read View:决定你能看到哪个版本
有了版本链,接下来的关键问题是:我该看哪一个版本?这就是 Read View(一致性视图) 的职责。
Read View 包含四个核心信息:
| m_ids | 当前系统中所有活跃(未提交)的事务 ID 列表 |
| min_trx_id | m_ids 中的最小值 |
| max_trx_id | 系统准备分配给下一个事务的 ID |
| creator_trx_id | 创建该 Read View 的事务 ID |
可见性判断逻辑
当一个事务尝试读取某行记录时,会拿着该记录的 DB_TRX_ID 按以下规则判断:
- 在 m_ids 中 → 还没提交,不可见
- 不在 m_ids 中 → 已提交,可见
如果当前版本不可见,就顺着 DB_ROLL_PTR 往版本链上回溯,直到找到一个可见的版本。
事务隔离级别
并发带来的三大问题
数据库并发处理就像多个人同时编辑一个文档,不加控制会出现三个经典问题:
| 脏读 | 读到了其他事务未提交的数据,如果对方回滚,读到的就是"假数据" |
| 不可重复读 | 同一事务内两次读同一数据,结果不同(中间被别人改了) |
| 幻读 | 同一事务内两次查同一范围,发现多了或少了几行 |
四大隔离级别
SQL 标准定义了四个隔离级别,级别越高越安全,但并发性能越差:
| Read Uncommitted | ❌ | ❌ | ❌ | 裸奔,基本不用 |
| Read Committed (RC) | ✅ | ❌ | ❌ | MVCC:每条 SQL 生成 Read View |
| Repeatable Read (RR) | ✅ | ✅ | 部分解决 | MVCC:事务启动时生成 Read View |
| Serializable | ✅ | ✅ | ✅ | 全表加锁,完全串行 |
RC vs RR:Read View 生成时机的差异
这是 MVCC 最核心的知识点:
RC(Read Committed):每条 SELECT 都会重新生成 Read View。如果事务 B 在中途提交了,事务 A 下一次查询就能看到 B 的修改——这就是"不可重复读"的根源。
RR(Repeatable Read):只在第一条 SELECT 时生成 Read View,整个事务期间复用。即使事务 B 提交了,事务 A 的视野里它仍然是"未提交"状态——这就保证了可重复读。
关于其他两个级别
- Read Uncommitted:根本不使用 MVCC,直接读最新版本,会产生脏读
- Serializable:将所有 SELECT 隐式转换为 SELECT … FOR SHARE,主要靠锁来保证安全
面试高频问题
1. MySQL 的默认隔离级别是什么?
Repeatable Read (RR)。这与 Oracle、PostgreSQL 默认使用 RC 不同。MySQL 坚持用 RR 很大程度上是为了兼容早期的主从复制逻辑(Binlog 格式问题)。
2. RR 真的不能解决幻读吗?
MySQL 的 RR 通过间隙锁(Gap Lock) 在很大程度上解决了幻读:
- 普通 SELECT 靠 MVCC 让你"看不见"新插入的数据
- SELECT … FOR UPDATE 靠间隙锁"锁住"插入位置,让别人插不进来
只有在"先快照读,再当前读更新"这种特殊场景下,才会触发 MVCC 的弱点。
3. 为什么大厂往往把默认级别改为 RC?
- 死锁概率:RR 下的间隙锁极大增加了死锁概率
- 并发性能:RC 没有间隙锁,且有"半一致性读"优化,吞吐量更高
- 业务容忍度:大多数互联网业务并不在乎那一点"不可重复读",或者可以通过乐观锁等业务手段补偿
总结
| 隐藏字段 | DB_TRX_ID + DB_ROLL_PTR 构成版本追踪基础 |
| Undo Log | 存储历史版本,支撑回滚和 MVCC |
| Read View | 决定事务能看到哪些版本 |
| RC vs RR | Read View 生成时机不同,决定是否可重复读 |
| 间隙锁 | MySQL RR 级别解决幻读的关键机制 |
网硕互联帮助中心



评论前必须登录!
注册