💱 事务与 MVCC
ACID · 隔离级别 · 脏读/不可重复读/幻读 · MVCC 原理 · ReadView 生成时机
1. 事务的 ACID 特性?分别由什么机制保证?
- 原子性(Atomicity):全成功或全回滚。由 undo log 保证(回滚日志,记录反向操作)
- 一致性(Consistency):事务前后数据满足约束。由应用层 + 其他三个特性共同保证(如唯一约束、外键)
- 隔离性(Isolation):并发事务互不干扰。由锁 + MVCC 保证
- 持久性(Durability):提交后不丢失。由 redo log 保证(崩溃恢复)
🎯 面试要点
- 经典问答:"ACID 分别靠什么实现"——undo(原子)、锁+MVCC(隔离)、redo(持久)
- 一致性是结果,其他三个是手段,面试时点一句"一致性靠约束+三者共同保证"加分
2. 四种隔离级别与三个并发问题?
三个并发问题:
- 脏读:读到其他事务未提交的数据(可能被回滚)
- 不可重复读:同一事务内两次读同一行,结果不同(其他事务提交了 UPDATE)
- 幻读:同一事务内两次范围查询,行数不同(其他事务提交了 INSERT/DELETE)
四种级别(隔离性递增,性能递减):
| 级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 有 | 有 | 有 |
| READ COMMITTED | 无 | 有 | 有 |
| REPEATABLE READ(MySQL 默认) | 无 | 无 | 基本无* |
| SERIALIZABLE | 无 | 无 | 无 |
*InnoDB 的 RR 级别通过 MVCC + 间隙锁/临键锁也能防止幻读(标准 SQL 说 RR 会幻读,但 InnoDB 实现更强)——这是高频考点。
🎯 面试要点
- Oracle 默认 RC,MySQL 默认 RR(历史原因:早期 binlog 复制格式下 RR 更安全)
- 普通 SELECT 靠 MVCC 快照读防幻读;当前读(SELECT ... FOR UPDATE)靠临键锁防幻读
- 应用层判断:RC 还是 RR 由业务一致性要求决定,MySQL 8 可设 binlog=row 后放心用 RC(并发更高)
3. MVCC 原理?(必考,能画图讲)
MVCC(多版本并发控制):让读写不互斥。核心三件套:
- 隐藏列:每行有 trx_id(最近修改事务 ID)、roll_pointer(指向 undo log 版本链)
- undo log 版本链:每次 UPDATE 生成一个旧版本(undo 记录),通过 roll_pointer 串成链,头是最新版本
- ReadView(读视图):事务做快照读时生成,记录此刻活跃事务列表。四个字段:
- m_ids:生成时刻活跃事务 ID 列表
- min_trx_id:最小活跃 ID
- max_trx_id:系统下一个待分配的事务 ID(大于当前所有已分配 ID)——注意不是 m_ids 最大值 + 1,因为可能存在"已提交且 ID 更大"的事务
- creator_trx_id:自己
可见性判断(遍历版本链找第一个可见版本):
- trx_id == creator → 自己改的,可见
- trx_id < min_trx_id → 已提交,可见
- trx_id >= max_trx_id → 未来事务(ReadView 之后才开的),不可见
- min ≤ trx_id < max → 在 m_ids 中(活跃未提交)不可见,不在则已提交可见
快照读与当前读
-- 快照读(普通 SELECT):走 MVCC,不加锁,读到 ReadView 对应的版本
SELECT * FROM user WHERE id = 1;
-- 当前读(下面这些):走最新版本 + 加锁
SELECT * FROM user WHERE id = 1 FOR UPDATE; -- 行级写锁
SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE; -- 共享锁
INSERT / UPDATE / DELETE
🎯 面试要点
- RC:每次 SELECT 都生成新 ReadView → 看到最新已提交 → 不可重复读
- RR:事务第一次 SELECT 生成 ReadView 后复用 → 快照不变 → 可重复读
- 快照读 + 当前读混用会出"更新丢失"类问题(先快照读再 update,更新基于旧值)→ 用 FOR UPDATE 锁行
4. InnoDB 在 RR 下如何彻底防幻读?什么情况还会幻读?
- 快照读防幻读:ReadView 复用,新插入的行(trx_id 大于 max)不可见
- 当前读防幻读:临键锁(Next-Key Lock)= 记录锁 + 间隙锁,锁定范围及间隙,其他事务无法在范围内插入 → 防幻读
- 仍可能幻读的场景:快照读和当前读混用(先 SELECT 后 SELECT FOR UPDATE,中间其他事务插入了行 → 当前读看到新行)
🎯 面试要点
- 临键锁锁的是"左开右闭区间"(如 (10, 20])
- gap 锁只在 RR 及以上级别生效(RC 下只锁记录)
- 高并发下 RR 的间隙锁容易死锁,RC + 应用层防重是常见取舍(配合乐观锁/唯一索引)