MySQL 事务 · MVCC · 锁
ACID 分别是什么?InnoDB 靠什么保证?
- 原子性(A):事务要么全做,要么全不做。靠unlog保证,回滚时逆向恢复数据。
- 一致性(C):事务前后数据约束不被破坏。靠unlog,relog,约束等共同保证。
- 隔离性(I):事务之间互不打扰。靠MVCC(快照读)和锁机制(当前读)保证。
- 持久性(D):事务提交后数据永久保存。靠redo log保证,崩溃后重放恢复。
四种隔离级别分别解决什么问题?
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交(RU) | ❌ | ❌ | ❌ |
| 读已提交(RC) | ✔ | ❌ | ❌ |
| 可重复读(RR) | ✔ | ✔ | ❌ |
| 串行化 | ✔ | ✔ | ✔ |
为什么 InnoDB 默认选 RR ?
RR是并发性能和数据一致性之间的最佳缓冲
- 比串行化,不用对所有读加锁,并发度更高
- 比RC,利用MVCC让普通查询实现了可重复读,提高了数据一致性
MVCC 核心机制(undo log + ReadView)
底层数据结构
每行数据都有三个隐藏字段
- DB_TRX_ID:最近修改该行数据的事务ID
- DB_ROLL_PTR:回滚指针,指向unlog中的旧版本
- DB_ROW_ID:行ID(无主键时用于聚簇索引)
ReadView(快照判断器)的核心的4个值
- m_ids:生成快照时,所有活跃(未提交)的事务ID列表
- min_trx_id:活跃事务中的最小ID
- max_trx_id:系统下一个将要分配的事务ID
- creator_trx_id:当前事务自己的ID
可见性判断逻辑(针对数据行的trx_id)
- 如果trx_id<min_trx_id,说明该事务在快照前已经生成->可见
- 如果trx_id>max_trx_id,说明该事务在快照后生成->不可见
- 如果在中间区间[min,max):
3.1 在m_ids列表中->不可见
3.2 不在m_ids列表中->可见
RC和RR的本质区别(生成时机)
- RC,每次执行select都会生成一个新的的Readview,所以可以看到其它事务提交的最新数据,导致不可重复读
- RR,只在事务第一次执行快照读时生成Readview,后续复用这一个,整个事务看到的数据视图保持一致
RR 如何解决幻读?
RR级别下,普通查询(快照读)必须通过MVCC来解决幻读,当前读必须靠锁来解决幻读(for update等):
- 临键锁:记录锁+间隙锁
- 它锁定的是左开右闭的区间
间隙锁只在>=RR以上级别存在
补充:
场景题:事务 A 执行 SELECT(读到 id=1 的余额为 100);随后事务 B UPDATE id=1 余额=200 并 commit;然后事务 A 再次 SELECT。请问:
- RC 下两次读到的值分别是多少?为什么?
- RR 下两次读到的值分别是多少?为什么?
- 如果 A 的第二次查询是 SELECT ... FOR UPDATE(当前读),RR 下会读到什么?为什么?
答案:
- RC:
A 第一次读到 100;B 提交 200 后,A 第二次 SELECT 新建 ReadView → 读到 200。两次不同 = 不可重复读。 - RR:
A 第一次 SELECT 生成的 ReadView 事务内复用,B 的 200 对 A 不可见 → 第二次仍读 100。两次相同 = 可重复读。
RR + FOR UPDATE(当前读):不走快照,直接读最新已提交值 → 读到 200,并对记录加锁(可能升级为 Next-Key Lock)。 - 版本链遍历:
行被 update 3 次 → 版本链 = 原值 + 3 个历史版本,共 4 个版本,各自记录 DB_TRX_ID;快照读从最新版本沿 DB_ROLL_PTR 依次往前查,返回第一个"对当前 ReadView 可见"的版本。
原创
MySQL 事务 · MVCC · 锁
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法