MVCC底层原理

MVCC底层原理

Administrator 725 2023-09-01

概念

MVCC是多版本并发控制

它是由两部分组成的

  • undo log 版本链
    • 当事务中有操作对数据进行修改的时候,就会将原数据存放到 undo log中,用于回滚或查询
    • 其中需要注意的是数据库中的数据表的每一条数据都有两个隐藏的字段
      • trx_id 更新该数据的事务id
      • roll_pointer 更新该数据的上一条原始数据的id
  • read view 读视图
    • 在查询数据时生成的数据
    • 由四部分组成
      • m_ids 当前活跃的事务id,即未提交事务的id列表
      • min_trx_id 未提交事务中最小的id,即这些未提交事务操作的数据的最原始的数据行的操作事务id
      • max_trx_id 未提交事务中最大的事务id+1,即下一个将要分配的事务id
      • creator_trx_id 创建该视图的事务id

规则

  • 一个事务只能看到第一次查询之前已经提交的事务以及当前事务的修改
  • 一个事务不能看到当前事务第一次查询之后创建的事务,以及未提交的事务的修改

读提交 RC

  1. 如下图所示,当事务的隔离级别位读提交时,每次查询数据时都会产生一个 read view
  2. 当因为每次查询都会产生新的 read view,当前一次查询数据时,使用最小事务id获取到原始数据,过了一段时间后,该事务再次进行查询时,可能会因为之前活跃的事务中已经有事务进行了提交,导致最小事务id发生了变化,获取到了新的原始数据,导致两次查询到的数据不一致,就会导致幻读,即不可重复读现象的发生。
  3. 因为每次查询时都产生新的read view,所以每次的活跃事务和最小事务等都会随着其他事务的提交而发生变化,就会导致在该事务的不同阶段,查询的结果也会不断发生变化。

image-20230901153026329

可重复读 RR

与读提交不同的是,他是在事务开启的时候,就会去创建一个 read view 之后的每一次查询都是复用之前创建的 read view

如此的话,每次查询时,使用的都是同一个事务id,故而从 undo log 中查询到的原始数据也是同一个了

image-20230901165602351

但是,RR并不能完全解决不可重复读的问题,原因如下:

在mysql的读操作中,分为两种类型

  • 快照读 select语句在不加锁的情况下就是 快照读

    • select * from xxx_table where ...
      
  • 当前读 加锁的 select ,或者对数据进行增删改都会进行 当前读

    • select * from xxx_table Lock In Share Mode;
      select * from xxx_table for update;
      insert into xxx_table...
      delete from xxx_table...
      update xxx_table...
      

当进行当前读的时候,会根据条件加 临键锁 (Next-Key Lock) ,当要操作的数据在临键锁的范围覆盖了其他事务的数据,那么将再次生成新的 read view 这时,可能会出现不可重复读的问题,如果要操作的数据不在临键锁的范围内,那么将复用一开始的 read view,则不会出现幻读。

综上,在RR隔离级别下,快照读完全解决了幻读,当前读部分解决了幻读,因为当前读不是读的快照,而是直接读取的内存

image-20230901171456013

mysql如何解决幻读问题

在RR,也就是可重复的一个事务隔离级别,InnoDB采用MVCC机制解决幻读问题,MVCC就是一种乐观锁的一个机制,他是通过对于不同事务生成不同的快照版本,然后通过undo版本连来进行管理。并且在MVCC里面,他规定了,高版本能够看到低版本的事务变更,低版本看不到高版本的事务变更,从而实现了不同事务之间的数据隔离,解决了幻读问题。但是如果在事务里面,存在当前读的情况下,他是直接读取内存里面的数据,跳过了快照读,所以还是会出现一个幻读的问题。可以通过两种方式来解决,尽量避免当前读的情况;可以引入LBCC的方式来解决。