MVCC(Multi-Version Concurrency Control):多版本并发控制,是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
MVCC 可以认为MVCC是行级锁的一个变种,在 MySQL InnoDB 中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。

特点:

  • MySQL 中 InnoDB 引擎支持 MVCC
  • 应对高并发事务, MVCC 比单纯的加行锁更有效, 开销更小
  • MVCC 在读已提交(Read Committed)和可重复读(Repeatable Read)隔离级别下起作用
  • MVCC 既可以基于乐观锁又可以基于悲观锁来实现

关键知识点

当前读和快照读

当前读:像select lock in share mode(共享锁),select for update;udate;insert;delete;(排他锁)都是当前读,读取的是记录的最新版本。读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。

快照读:不加锁的select操作就是快照读,即不加锁的非阻塞读。快照读的前提是隔离级别不是串行级别,串行级别下快照读会退化为当前读。之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销。既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是历史版本。

行隐藏字段

MVCC的实现,是通过保存数据在某个时间点的快照来实现的。也就是说,不管需要执行多长时间,每个事务看到的数据都是一致的。根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。

InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的:

  • DB_TRX_ID:记录创建这条记录最后一次修改该记录的事务ID
  • DB_ROLL_PTR:回滚指针,指向undo log这条记录的上一个版本

版本链:
多个事务并行操作某一行数据时,不同事务对该行数据的修改会产生多个版本,然后通过回滚指针(roll_pointer),连成一个链表,这个链表就称为版本链。如下:
image.png

ReadView

ReadView是事务在进行快照读的时候生成的记录快照, 可以帮助我们解决可见性问题的

如果一个事务要查询行记录,需要读取哪个版本的行记录呢? ReadView 就是来解决这个问题的。 ReadView 保存了当前事务开启时所有活跃的事务列表。换个角度,可以理解为: ReadView 保存了不应该让这个事务看到的其他事务 ID 列表。

ReadView是如何保证可见性判断的呢?我们先看看 ReadView 的几个重要属性

  • trx_ids: 当前系统中那些活跃(未提交)的读写事务ID, 它数据结构为一个List。(重点注意:这里的trx_ids中的活跃事务,不包括当前事务自己和已提交的事务,这点非常重要)
  • low_limit_id: 目前出现过的最大的事务ID+1,即下一个将被分配的事务ID。
  • up_limit_id: 活跃事务列表trx_ids中最小的事务ID,如果trx_ids为空,则up_limit_id 为 low_limit_id。

访问某条记录的时候如何判断该记录是否可见,具体规则如下:

  • 如果被访问版本的 事务ID = creator_trx_id,那么表示当前事务访问的是自己修改过的记录,那么该版本对当前事务可见;
  • 如果被访问版本的 事务ID < up_limit_id,那么表示生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的 事务ID > low_limit_id 值,那么表示生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
  • 如果被访问版本的 事务ID在 up_limit_id和m_low_limit_id 之间,那就需要判断一下版本的事务ID是不是在 trx_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;

image.png
这里需要思考的一个问题就是 何时创建ReadView?
上面说过,ReadView是来解决一个事务需要读取哪个版本的行记录的问题的。那么说明什么?只有在select的时候才会创建ReadView。但在不同的隔离级别是有区别的:
在提交读隔离级别下,是每个select都会创建最新的ReadView;而在可重复读隔离级别下,则是当事务中的第一个select请求才创建ReadView(下面会详细举例说明)。

那insert/update/delete操作呢?
这样操作不会创建ReadView。但是这些操作在事务开启(begin)且其未提交的时候,那么它的事务ID,会存在在其它存在查询事务的ReadView记录中,也就是trx_ids中。

MVCC实现原理分析

如何查询一条记录

  1. 获取事务自己事务ID,即trx_id。(这个也不是select的时候获取的,而是这个事务开启的时候获取的,也就是begin的时候)
  2. 获取ReadView(这个才是select的时候才会生成的)
  3. 数据库表中如果查询到数据,那就到ReadView中的事务版本号进行比较。
  4. 如果不符合ReadView的可见性规则, 即就需要Undo log中历史快照,直到返回符合规则的数据;

InnoDB 实现MVCC,是通过ReadView+ Undo Log 实现的,Undo Log 保存了历史快照,ReadView可见性规则帮助判断当前版本的数据是否可见
[

](https://blog.csdn.net/yudiandemingzi/article/details/122246902)