多版本并发控制
Multi-Version Concurrency Control
MVCC 可以不采用锁机制,而是通过乐观锁的方式来解决
不可重复读
和幻读
问题
可以在并发情况下,不使用锁来解决读写冲突问题。
MVCC 的目的是解决读写锁造成的多个、长时间的读操作饿死写操作问题。每个事务读到的数据项都是一个快照(快照读),并依赖于实现的隔离级别(非 serializable
)。
写操作不覆盖已有数据项,而是创建一个新的版本,直至所在操作提交时才变为可见。
MVCC 主要是通过 隐式字段、undo日志、read view 来实现的。
当前读和快照读
当前读
当前读指的是读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
select ... from xxx lock in shard mode
、select ... from xxx for update
、
update from xxx set xxx=xxx
、insert 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
read view 读视图
事务进行快照读操作产生的读视图(read view)。在该事务执行快照读的瞬间,会生成当前数据库系统的一个快照,记录并维护系统当前活跃事务的 ID。
MVCC 的整体流程
当事务 B 对某行数据进行了修改,数据库为该行数据生成了一个 read view 读视图,假设当前事务 ID 为 2 ,此时还有事务 A 和事务 C 在活跃中,事务 D 在事务 B 快照读前一刻提交了更新,所以 read view 记录了系统当前活跃事务 A,C 的 ID,维护在一个列表上。
事务A | 事务B | 事务C | 事务D |
---|---|---|---|
事务开始 | 事务开始 | 事务开始 | 事务开始 |
... | ... | ... | 修改且已提交 |
进行中 | 快照读 | 进行中 | |
... | ... | ... |