跳到主要内容

多版本并发控制

Multi-Version Concurrency Control

MVCC 可以不采用锁机制,而是通过乐观锁的方式来解决 不可重复读幻读 问题

可以在并发情况下,不使用锁来解决读写冲突问题。

MVCC 的目的是解决读写锁造成的多个、长时间的读操作饿死写操作问题。每个事务读到的数据项都是一个快照(快照读),并依赖于实现的隔离级别(非 serializable)。 写操作不覆盖已有数据项,而是创建一个新的版本,直至所在操作提交时才变为可见。

MVCC 主要是通过 隐式字段、undo日志、read view 来实现的。

当前读和快照读

当前读

当前读指的是读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。

select ... from xxx lock in shard modeselect ... from xxx for updateupdate from xxx set xxx=xxxinsert into ... values ...delete xxx where xxx=xxx 都是当前读

快照读(一致性读)

不加锁的 select 操作就是快照读,即不加锁的非阻塞;

快照读的前提是隔离级别不是 serializable,serializable 隔离级别下的快照读会退化为当前读。

可重复读与提交读的差别?

可重复读在创建一个快照和 read view 并且下次快照读时,使用的还是同一个 read view,所以其它事务修改数据对它时不可见的。
提交读则是每次快照读时都会产生新的快照读和 read view,所以就会产生不可重复的问题。

InnoDB 下,可重复度和提交读快照读生成的时机

  • 可重复读级别下的事务,在对某条记录的第一次快照读时会创建一个read view,之后的快照读都会使用这个 read view。
  • 提交读级别下的事务,每次快照读都会创建一个新的 read view。

隐式字段

隐式字段指的是,一行记录中除了我们自己定义的字段外,还有 InnoDB 添加的隐式字段。

  • DB_ROW_ID 6 byte,隐含的自增 ID,如果数据表没有主键,InnoDB 会自动以 DB_ROW_ID 来生成一个聚簇索引。
  • DB_TRX_ID 6 byte, 最近修改事务 ID,记录创建/修改这条记录的事务ID
  • DB_ROLL_PTR 7 byte,回滚指针,指向这条记录的上一个版本(存储在 rollback segment 里),配合 undo log,指向上一个旧版本。
  • DELETE_BIT 1 byte,记录被更新或删除并不代表真的删除,而是删除 flag 变了

undo log

undo log

read view 读视图

事务进行快照读操作产生的读视图(read view)。在该事务执行快照读的瞬间,会生成当前数据库系统的一个快照,记录并维护系统当前活跃事务的 ID。

MVCC 的整体流程

当事务 B 对某行数据进行了修改,数据库为该行数据生成了一个 read view 读视图,假设当前事务 ID 为 2 ,此时还有事务 A 和事务 C 在活跃中,事务 D 在事务 B 快照读前一刻提交了更新,所以 read view 记录了系统当前活跃事务 A,C 的 ID,维护在一个列表上。

事务A事务B事务C事务D
事务开始事务开始事务开始事务开始
.........修改且已提交
进行中快照读进行中
.........