mysql中的MVCC

介绍

  MVCC全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。

当前读和快照读

  • 当前读: 它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。像select lock in share mode(共享锁); select for update, update, insert, delete(排他锁)这些都是当前读。
  • 快照读: 像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行化,串行化会使得快照读退化成当前读。(串行化下,一切都是串行,不存在并发的情况)

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

  MVCC就是为了实现读写冲突不加锁,这里的读是快照读,当前读是一种加锁操作,是一种悲观锁。

MVCC与读操作

  MVCC指的是维持一个数据的多个版本,使得读写操作没有冲突
  快照读就是MySQL为我们实现MVCC理想模型的一个具体功能;而当前读就是悲观锁的表现。
  MVCC模型在MySQL中的具体实现则是由3个隐式字段,undo日志,Read View等去完成的。

解决问题

数据库的并发情况

  数据库的并发情况分为三种:

  • 读-读:不存在任何问题,不需要并发控制。
  • 读-写:有线程安全问题,可能会造成事务隔离性问题,脏读、幻读、不可重复读。
  • 写-写:有线程安全问题,可能丢失更新,第一类、第二类丢失更新。

MVCC作用

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

  • 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作不用阻塞读操作,提高了数据库并发读写的性能。
  • 同时还可以解决脏读、幻读、不可重复读的问题,但不能解决丢失更新。

  可以使用MVCC+悲观锁/乐观锁来解决并发冲突问题。


原理

  MVCC的目的就是多版本并发控制,在数据中就是为了解决读写冲突。
  的实现原理主要是依赖记录中的 3个隐式字段,undo日志,Read View来实现的。所以我们先来看看这个三个point的概念。

隐式字段

  数据库表除了我们定义的字段外,还有三个隐式字段:

  • DB_TRX_ID:6Byte,最近修改或插入的事务ID,记录创建这条记录/最后一次修改该记录的事务ID。
  • DB_ROLL_PTR:7Byte,回滚指针,指向这条记录的上一个版本。
  • DB_ROW_ID:6Byte,隐含的自增主键,我们在没有指定主键的时候Innodb会使用隐式主键产生聚簇索引。

undo日志

  undo log日志主要分为两种:

  • insert undo log:代表事务在插入新纪录时产生的undo log,只在事务回滚时需要,并且在事务提交后可以被立即丢弃。
  • uodate undo log:事务在修改或者删除时产生的undo log,不仅在事务回滚时需要,在快照读时也需要,所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除。

Read View

  Read View是在事务进行快照读的时候产生的,该事务会在快照读的那一刻会生成数据库事务的快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)。
  Read View主要是用来做可见性判断的,即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。
  Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本。
  不同的隔离级别生成Read View的时间是不一样的,可重复读是第一次读的时候生成的,之后每一次都是第一次的Read View,而读已提交则是每次都会生成。(这里是指当前读的时候)

总结

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


参考文献 & 鸣谢