MySQL 事务 · MVCC · 锁

ACID 分别是什么?InnoDB 靠什么保证?

  1. 原子性(A):事务要么全做,要么全不做。靠unlog保证,回滚时逆向恢复数据。
  2. 一致性(C):事务前后数据约束不被破坏。靠unlog,relog,约束等共同保证。
  3. 隔离性(I):事务之间互不打扰。靠MVCC(快照读)和锁机制(当前读)保证。
  4. 持久性(D):事务提交后数据永久保存。靠redo log保证,崩溃后重放恢复。

四种隔离级别分别解决什么问题?

隔离级别脏读不可重复读幻读
读未提交(RU)
读已提交(RC)
可重复读(RR)
串行化

为什么 InnoDB 默认选 RR ?

RR是并发性能和数据一致性之间的最佳缓冲

  1. 比串行化,不用对所有读加锁,并发度更高
  2. 比RC,利用MVCC让普通查询实现了可重复读,提高了数据一致性

MVCC 核心机制(undo log + ReadView)

底层数据结构

每行数据都有三个隐藏字段

  1. DB_TRX_ID:最近修改该行数据的事务ID
  2. DB_ROLL_PTR:回滚指针,指向unlog中的旧版本
  3. DB_ROW_ID:行ID(无主键时用于聚簇索引)

ReadView(快照判断器)的核心的4个值

  1. m_ids:生成快照时,所有活跃(未提交)的事务ID列表
  2. min_trx_id:活跃事务中的最小ID
  3. max_trx_id:系统下一个将要分配的事务ID
  4. creator_trx_id:当前事务自己的ID

可见性判断逻辑(针对数据行的trx_id)

  1. 如果trx_id<min_trx_id,说明该事务在快照前已经生成->可见
  2. 如果trx_id>max_trx_id,说明该事务在快照后生成->不可见
  3. 如果在中间区间[min,max):
    3.1 在m_ids列表中->不可见
    3.2 不在m_ids列表中->可见

RC和RR的本质区别(生成时机)

  1. RC,每次执行select都会生成一个新的的Readview,所以可以看到其它事务提交的最新数据,导致不可重复读
  2. RR,只在事务第一次执行快照读时生成Readview,后续复用这一个,整个事务看到的数据视图保持一致

RR 如何解决幻读?

RR级别下,普通查询(快照读)必须通过MVCC来解决幻读,当前读必须靠锁来解决幻读(for update等):

  1. 临键锁:记录锁+间隙锁
  2. 它锁定的是左开右闭的区间
    间隙锁只在>=RR以上级别存在

补充:

场景题:事务 A 执行 SELECT(读到 id=1 的余额为 100);随后事务 B UPDATE id=1 余额=200 并 commit;然后事务 A 再次 SELECT。请问:

  • RC 下两次读到的值分别是多少?为什么?
  • RR 下两次读到的值分别是多少?为什么?
  • 如果 A 的第二次查询是 SELECT ... FOR UPDATE(当前读),RR 下会读到什么?为什么?

答案:

  1. RC:
    A 第一次读到 100;B 提交 200 后,A 第二次 SELECT 新建 ReadView → 读到 200。两次不同 = 不可重复读。
  2. RR:
    A 第一次 SELECT 生成的 ReadView 事务内复用,B 的 200 对 A 不可见 → 第二次仍读 100。两次相同 = 可重复读。
    RR + FOR UPDATE(当前读):不走快照,直接读最新已提交值 → 读到 200,并对记录加锁(可能升级为 Next-Key Lock)。
  3. 版本链遍历:
    行被 update 3 次 → 版本链 = 原值 + 3 个历史版本,共 4 个版本,各自记录 DB_TRX_ID;快照读从最新版本沿 DB_ROLL_PTR 依次往前查,返回第一个"对当前 ReadView 可见"的版本。