前言
阅读建议:快速略读本文后,切换到更严谨的文章:【高并发基础】理解 MVCC 及提炼实现思想
阅读本文必须提前了解:当前读和快照读的区别
1. 隐藏字段
MySQL 的 InnoDB 会维护一系列不暴露给用户的隐藏字段,其中有3个用于实现快照读(非阻塞读)
-
DB_TRX_ID
最新一次修改本行记录的操作ID
同一个事务,同一个操作要加以区分,这里指操作 -
DB_ROLL_PTR
回滚指针,存储的地址配合undo log -
DB_ROW_ID
InnoDB 无论有没有主键,都会通过该字段唯一标识一条记录
2. undo log 执行流程
- Session A 修改 Field2 12 -> 32, 首先会对该行数据加上 写锁 (也叫排他锁)。DB_TRX_ID = NULL + 1,标识是 Session A 的一修改操作 ; 然后将 Field2 12 的信息拷贝至 undo log , 用回滚指针指向undo log 刚创建的记录。
- 并发情况下,Session B 修改 Field3 13 -> 45, 发现DB_TRX_ID = 1,证明已有其他事务操作过,利用DB_TRX_ID = DB_TRX_ID + 1 标识 Session B 的操作。多事务并发操作下,DB_TRX_ID递增,回滚指针 DB_ROLL_PTR 指向 undo log的数据,形成链表的数据结构。
越新开启的事务操作,DB_TRX_ID的值越大
3. undo log 与 read view(快照)
- read view 用于做可见性判断,算法简略描述:
- 操作某条记录,先生成DB_TRX_ID。将自身记录的 DB_TRX_ID 与 当前活跃事务 的 DB_TRX_ID 进行比较
- 找到 DB_TRX_ID >= 当前活跃事务操作 的DB_TRX_ID 的上边界,比如 undo log 内有 DB_TRX_ID = 4, 3 ,2 ,1; 而当前的DB_TRX_ID 为 3,则上边界为3。
- 使用上边界,找到undo log 的记录,读取其所有Field的记录,当且仅当能看到并能修改上边界为3所暂存的记录。
4. RC、RR 快照读的不同
-
READ-COMMITTED
一个事务下,RC 每条 dml 语句(增删改查)读数据都会创建一个新的 read view (快照),其DB_TRX_ID 每次读都 +1。 -
REPEATABLE-READ
一个事务下,RR 第一条 dml 会对已存在的 undo log 按read view的规则创建一个快照。同一个事务下连续的多个select,也是只能读到同一个快照,即第一个快照的内容。
一个事务下如果对记录进行了 update 操作, 快照也会被更新,再次select 时事务ID一致,但是快照不同数据更新,快照更新,是符合当前事务下自己业务逻辑的
也就是 select + N*select + select 中间状态不会被别的事务改变
而 select + 其他事务的 update + 自己事务的 update + select 会合并其他事务的update,并更新快照
R 可以合并其他事务的 update 操作,看似与可重复读的矛盾之处
因为 RR 和 RC 下所有 update 操作都是当前读,不去读快照,读的是实时数据并加锁。
自己更新的数据,自己能感知到,看【高并发基础】理解 MVCC 及提炼实现思想
5. 综上
RR 的事务严格控制MVCC中的版本,让RR的事务下,不能读到未提交的数据
只能读到提交了的数据。并且RR下重复快照读,数据是一致的,所以称之为可重复读的隔离级别。
为了保证当前业务的逻辑正确,RR可以借助当前读,刷新快照,此时再读就是不一样的数据。
6. 总结
undo log 实现了快照读的数据结构。
read view (快照)实现主要的快照算法。
READ-COMMITTED 拥有细化到语句粒度的建立快照的能力。
REPEATABLE-READ只拥有事务粒度的建立快照的能力