浅谈MySQL:MVCC的作用及原理

什么是MVCC

MVCC全称是Multi-Version Concurrency Control,即多版本并发控制,主要是为了提高数据库的并发读写性能。

当我们并发读写同一行数据的时候,为了防止出错,需要对数据进行加锁操作,但这并不是一个高效的操作,很容易造成操作请求阻塞超时。而MVCC采用了更好的方式去处理并发读写请求,做到在发生读写请求冲突时不用加锁。这个读是指的快照读,而不是当前读。

那么什么是当前读,什么又是快照读?我们下面来学习一下。

当前读

当前读就是读取数据库记录的最新版本,会对当前读取的数据进行加锁,防止其他事务修改数据,是悲观锁的一种操作。

以下几种情况是当前读:

  • select 语句加锁是当前读

    # 共享锁
    select a from t where id = 1 lock in share mode;
    
    #排他锁
    select a from t where id = 1 for update;
    
  • update 语句是当前读

    update t set a = a + 1;
    

快照读

快照读的实现是基于多版本并发控制,即MVCC,快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本的数据。

以下几种情况是快照读:

  • 在默认隔离级别下,select 语句默认是快照读

    select a from t where id = 1;
    

快照读与MVCC的关系

MVCC是”维持一个数据的多个版本,使读写操作没有冲突”的一个抽象概念。

这个概念需要具体功能去实现,这个具体实现就是快照读。

MVCC解决并发哪些问题

MVCC用来解决读-写冲突的无锁并发控制,就是为事务分配单向增长的时间戳。为每个数据修改保存一个版本,版本与事务时间戳相关联。读操作只读取该事务开始前的数据库快照。

解决问题如下:

  • 并发读-写时:可以做到读操作不阻塞写操作,同时写操作也不会阻塞读操作。

  • 解决脏读、幻读、不可重复读等事务隔离问题,但不能解决写-写更新丢失问题。因此需要下面提高并发性能的组合:

    • MVCC + 悲观锁:MVCC解决读写冲突,悲观锁解决写写冲突
    • MVCC + 乐观锁:MVCC解决读写冲突,乐观锁解决写写冲突

MVCC的实现原理

MVCC主要是版本链,undo日志 ,Read View 来实现的

版本链 & undo日志

我们数据库中的每行数据都有如下几个隐藏字段:

字段名 占用空间 说明
trx_id 6byte 最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID
roll_pointer 7byte 回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
row_id 6byte 隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以db_row_id产生一个聚簇索引

版本链的结构如下图所示,每次修改都会在这个链条后面增加一个版本,trx_id就代表最新这条记录的ID。而历史数据都会被存入undo日志,通过回滚指针roll_pointer可以找到数据行的前一个版本,以供事务回滚时使用。
image

Read View

事务进行快照读操作的时候生产的读视图(Read View),在该事务执行快照读的那一刻,会生成数据库系统当前的一个快照。通过Read View,事务可以判断版本链中的哪个版本是可用的。

Read View中保存的字段信息如下:

字段名 说明
m_ids 表示在生成Read View时,当前系统中活跃的读写事务的事务ID列表
min_trx_id 表示在生成Read View时,当前系统中活跃的读写事务中最小的事务ID,也就是m_ids中的最小值
max_trx_id 表示生成Read View时,系统中应该分配给下一个事务的ID值
creator_trx_id 表示生成该Read View的事务的事务ID

那么Read View是如何判断版本链中的哪个版本是可用的呢?通过以下4个条件就可以判断:

  • trx_id == creator_trx_id:可以访问这个版本
  • trx_id < min_trx_id:可以访问这个版本
  • trx_id > max_trx_id:不可以访问这个版本
  • min_trx_id <= trx_id <= max_trx_id:如果trx_idm_ids中是不可以访问这个版本的,反之可以访问

MVCC如何实现RC和RR

我们在上一篇中讲过RC(Read Committed,读提交)和RR(Repeatable Read,可重复读)隔离级别的定义,这两个隔离级别的实现都离不开MVCC。

RR、RC生成Read View的时机

  • RC隔离级别下,每个快照读都会生成并获取最新的Read View,因此可能出现在同一个事务中两次查询的结果不一致的情况
  • RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View,之后的快照读获取的都是同一个Read View,之后的查询就不会重复生成了,所以一个事务的查询结果每次都是一样的

解决幻读问题

  • 快照读:通过MVCC来进行控制的,不用加锁。按照MVCC中规定的“语法”进行增删改查等操作,以避免幻读。
  • 当前读:通过next-key锁(行锁+gap锁)来解决问题的。

总结

从以上的描述中,我们可以看出MVCC指的就是在使用RC、RR这两种隔离级别的事务在执行普通的SEELCT操作时访问记录的版本链的过程,MVCC可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。

posted @ 2021-11-28 22:35  远古时代的程序猿  阅读(1614)  评论(0编辑  收藏  举报