MVCC¶
每一行记录都有两个隐藏列:DATA_TRX_ID、DATA_ROLL_PTR。
- 获得事务 ID
- 获得 read view
- 查询到数据,与 read view 中的事务 ID 进行比较
- 符合要求则返回
- 不符合要求则从 undo log 链中寻找
DATA_TRX_ID¶
记录最近更新这条行记录的事务 ID,大小为 6 个字节
DATA_ROLL_PTR¶
表示指向该行回滚段(rollback segment)的指针,大小为 7 个字节,InnoDB 便是通过这个指针找到之前版本的数据。该行记录上所有旧版本,在 undo 中都通过链表的形式组织。
DB_ROW_ID¶
行标识(隐藏单调自增 ID),大小为 6 字节,如果表没有主键,InnoDB 会自动生成一个隐藏主键,因此会出现这个列。
undo log 链¶
Update 操作中:
- 对行记录加排他锁
- 把该行原本的值拷贝到
undo log中,DB_TRX_ID和DB_ROLL_PTR都不变 - 修改该行的值这时产生一个新版本,更新
DATA_TRX_ID为修改记录的事务ID - 将
DATA_ROLL_PTR指向刚刚拷贝到undo log链中的旧版本记录。如果对同一行记录执行连续的UPDATE,Undo Log会组成一个链表,遍历这个链表可以看到这条记录的变迁 - 记录
redo log,包括undo log中的修改
read view¶
在 RR 隔离级别下,每个事务 touch first read 时(本质上就是事务开始后执行的第一个 SELECT 语句时,后续所有的 SELECT 都是复用这个 ReadView,其它 update, delete, insert 语句和一致性读 snapshot 的建立没有关系),会将当前系统中的所有的活跃事务拷贝到一个列表生成ReadView。
在 RC 隔离级别下,每个 SELECT 语句开始时,都会重新将当前系统中的所有的活跃事务拷贝到一个列表生成 ReadView。二者的区别就在于生成 ReadView 的时间点不同,一个是事务之后第一个 SELECT 语句开始、一个是事务中每条 SELECT 语句开始。
判断可见性¶
ReadView 中是当前活跃的事务 ID 列表,称之为 m_ids,其中最小值为 up_limit_id,最大值为 low_limit_id,事务 ID 是事务开启时 InnoDB 分配的,其大小决定了事务开启的先后顺序,因此我们可以通过 ID 的大小关系来决定版本记录的可见性。
- 如果被访问版本的
trx_id小于 最小值up_limit_id,说明生成该版本的事务在ReadView生成前就已经提交了,所以该版本可以被当前事务访问。 - 如果被访问版本的
trx_id大于 最大值low_limit_id,说明生成该版本的事务在生成ReadView后才生成,所以该版本不可以被当前事务访问。需要根据Undo Log链找到前一个版本,然后根据该版本的 DB_TRX_ID 重新判断可见性。 - 如果被访问版本的
trx_id在最大值和最小值之间(包含),那就需要判断一下trx_id的值是不是在m_ids列表中。- 如果在,说明创建
ReadView时生成该版本所属事务还是活跃的,因此该版本不可以被访问,需要查找 Undo Log 链得到上一个版本,然后根据该版本的DB_TRX_ID再从头计算一次可见性; - 如果不在,说明创建
ReadView时生成该版本的事务已经被提交,该版本可以被访问。
- 如果在,说明创建