Mvcc并发控制

发布时间 2023-12-28 21:34:27作者: Stitches

原理

MVCC 实现主要依赖于数据行的三个隐藏字段、UndoLog、ReadView 来实现的。

首先对于任意一行数据,它都有如下三个隐藏字段:

  • DB_TRX_ID:最近修改的事务ID,记录修改或创建这条记录的最新事务ID;
  • DB_ROW_ID:如果数据没有主键,生成的一个隐藏的默认主键;
  • DB_ROLL_PTR:回滚指针,指向该条记录的上一个版本,需要配合 UndoLog 使用;

UndoLog

UndoLog 是回滚日志,在执行插入、更新、删除操作时会生成相反的 SQL 操作语句并存储到 UndoLog中。如果是插入语句,产生的 UndoLog 可以在事务提交后立刻丢弃,只在回滚时需要;而如果是 更新、删除语句,需要保留 UndoLog,因为不仅在事务回滚时需要,在快照读时也需要,所以不能随便删除。

数据更新或删除时只是更新一下数据行的 delete_bit等位,并不会真正删除这条数据。Mysql会有一个 purge线程来定时清理 delete_bit 为 TRUE 的数据。

假设有一个事务ID为1的事务向表中插入记录,此时的数据状态为:

name age gender DB_ROW_ID DB_TRX_ID DB_ROLL_PTR
zhangsan 23 man 1 1 null

此时有另一个事务对 name 字段修改,修改时会先对这行加排他锁,然后将旧数据拷贝到 UndoLog 日志中作为副本;拷贝完毕后更新这行数据,修改 name 值、DB_TRX_ID 值、DB_ROLL_PTR 指向 UndoLog 日志的副本记录,然后提交事务后释放锁。

所以如果多个事务操作同一行数据,会生成类似于链表的数据行,链头为最新的 UndoLog,链尾为最旧的 UndoLog。

ReadView

Read View 是事务进行快照读时产生的读视图,在该事务执行快照操作时会生成当前系统的快照,记录当前活跃的事务id。利用 Read View 来做可见性判断,能够对比当前数据行的 DB_TRX_IDRead View 中的属性,如果当前数据行不满足可见性条件,就通过 DB_ROLL_PTR 回滚到上一条记录接着判断,直到找到可见性的记录为止。

Read View 包括的三个核心属性如下:

  • trx_list:记录当前正在活跃的事务ID(1,2,3);
  • up_limit_id:记录 trx_list 中最小的事务ID (1);
  • low_limit_idReadView 生成时刻系统尚未分配的事务ID,记录系统将要生成的下一个事务ID(4);

Read View 会按照以下规则去比较 DB_TRX_ID 以找到符合可见性的那条数据:

  • DB_TRX_ID < up_limit_id:说明在最小ID的事务出现前,这条数据就已经提交了,则可以看见;反之继续;
  • DB_TRX_ID >= low_limit_id:只有在生成新的事务后才能看见这条记录,不可见;反之当前 DB_TRX_ID 介于二者之间,需要判断是否在活跃事务ID集合 trx_list 中来决定是否可见;
  • 如果在 trx_list 中,说明事务还未提交不可见;否则可见。

不同隔离级别下的MVCC

RC 隔离级别下

读已提交隔离级别下,每个快照读都会生成并获取到最新的 Read View。实例如下:

事务1 事务2 事务3 事务4
事务开始 事务开始 事务开始
修改并提交 .... ....
快照读 进行中
.... .... 事务开始
.... .... 修改未提交
快照读 .... ....

第一次快照读时,活跃的事务ID为(2,3),up_limit_id 为 (2),low_limit_id 为(4),因此只能读取到事务1的数据,读不到事务4的数据;

第二次快照读时,活跃事务ID为(2,3,4),up_limit_id 为(2),low_limit_id 为(5),但是此时事务4 为活跃事务,所以不可见,仍然只能读到事务1.

RR 隔离级别下

可重复读隔离级别下,在第一次快照读时创建 ReadView,之后每次快照读都用这个 Read View

事务1 事务2 事务3 事务4
事务开始 事务开始 事务开始
修改并提交 .... ....
快照读 进行中
.... .... 事务开始
.... .... 修改未提交
快照读 .... ....

在第一次快照读之后不管额外进行多少次快照读都会使用第一次快照 Read View 数据,活跃事务ID为(2,3),up_limit_id 为 (2),low_limit_id 为 (4).


使用场景

不同隔离级别实现多事务并发控制的方式不同:

  • 读未提交模式:直接返回记录上的最新值,没有试图概念;
  • 读已提交模式:通过 MVCC 在每次执行 SQL 语句时创建视图,同一条记录的多个事务操作会生成多个 UndoLog 日志副本,并按照事务执行顺序以 DB_ROW_PTR 为回滚指针连接起来。当某个事务执每次行读操作时,生成 Read View,依次匹配链表的每个节点直到找到匹配的可见 UndoLog 记录。
  • 可重复读模式:原理同读已提交模式,区别是可重复读只会执行一次快照读,之后的每次读操作都会重复利用第一次快照读的结果。
  • 串行化模式:使用加锁的方式来避免并行访问,使用比较少,并发性低。

可重复读使用场景

比如说两张表,消费记录表和余额表。当在做数据校对时希望能够处理用户新的交易数据但又不影响校对过程。这时可以将隔离级别设置为可重复读。

参考

https://juejin.cn/post/7102676257149550622