Dawn's Blogs

分享技术 记录成长

0%

MySQL高级 (11) 多版本并发控制

概述

多版本并发控制(Multi-Version Concurrency Control,MVCC),通过数据的多个版本管理来实现数据库的并发控制。

在MySQL中,可重复读是默认的隔离级别。可重复读隔离级别下,不仅解决了脏读不可重复读问题,因为使用了MVCC,所以还一定程度上解决了幻读的问题。

快照读

快照读又叫一致性读,读取的是快照数据不加锁的简单的SELECT语句,都属于快照读

之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于MVCC,它在很多情况下,避免了加锁操作,降低了开销。既然是基于多版本,那么快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。

当前读

当前读读取的是记录的最新版本的数据,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁加锁的SELECT语句,以及对数据进行增删改都会进行当前读

MVCC实现原理

MVCC的原理,就是生成一个ReadView,通过ReadView找到符合条件的记录版本。查询语句只能到生成ReadView之前已提交事务所做的更改。而写操作肯定针对的是最新的版本信息记录的历史版本和改动记录的最新版本并不冲突,也就是采用MVCC时,读-写并不冲突

普通的SELECT语句READ COMMITTEDREPEATABLE READ隔离级别下会使用到MVCC读取记录。

使用READ UNCOMMITTED隔离级别的事务,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好;而使用SERIALIZABLE隔离级别的事务,会以加锁的方式访问记录,所以也一定读到的是最新版本的记录。

READ COMNITTED

READ COMMITTED隔离级别下,一个事务在执行过程中每次执行SELECT操作时都会生成一个ReadView,ReadView的存在本身就保证了事务不可以读取到未提交的事务所做的更改,也就是避免了脏读现象。

REPEATABLE READ

REPEATABLE READ隔离级别下,一个事务在执行过程中只有第一次执行SELECT操作才会生成一个ReadView,之后的SELECT操作都复用这个ReadView,这样也就避免了不可重复读幻读的问题。

ReadView

ReadView 有四个重要的字段:

  • creator_trx_id:指的是创建该 Read View 的事务的事务 id
  • m_ids :指的是在创建 Read View 时,活跃但还没有提交的事务 id 列表
  • min_trx_id :指的是在创建 Read View 时,当前数据库中活跃事务中 id 最小的事务
  • max_trx_id:全局事务中最大的事务 id 值 + 1,也就是创建 Read View 时当前数据库中应该给下一个事务的 id 值

img

同时,在 InnoDB 中,一个数据行中还有两个隐藏列,trx_id 和 roll_pointer:

  • trx_id,当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里
  • roll_pointer,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录。

在创建 Read View 后,我们可以将记录中的 trx_id 划分这三种情况:

img

一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:

  • 如果记录的 trx_id 值小于 Read View 中的 min_trx_id 值,表示这个版本的记录是在创建 Read View 已经提交的事务生成的,所以该版本的记录对当前事务可见
  • 如果记录的 trx_id 值大于等于 Read View 中的 max_trx_id 值,表示这个版本的记录是在创建 Read View 才启动的事务生成的,所以该版本的记录对当前事务不可见
  • 如果记录的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之间,需要判断 trx_id 是否在 m_ids 列表中:
    • 如果记录的 trx_id m_ids 列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见
    • 如果记录的 trx_id 不在 m_ids 列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见

这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。