MVCC基础概念

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

MVCC在Innodb中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。

当前读

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

快照读

  • 提高数据库的并发查询能力

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

当前读、快照读、MVCC的关系

MVCC指维持一个数据的多个版本,使得读写操作没有冲突,快照读是mysql为实现MVCC的一个非阻塞读的功能,MVCC模块在Mysql中的具体实现由三个隐式字段、undo日志、read view 三个组件来实现的。

MVCC解决的问题是什么?

数据库的并发场景有三种,分别为:

  1. 读读:不存在任何问题,也不需要并发控制
  2. 读写:有线程安全的问题,可能会造成事务隔离性问题,可能会遇到脏读、幻读、不可重复读
  3. 写写:有线程安全问题,可能出现更新丢失的问题。

MVCC是一种用来解决读写冲突的无锁并发控制,也就是为事务分配单项增长的时间戳,为每个修改保存一个版本,版本和事务时间戳相关联,读操作只能该事务开始前的数据库的快照,所以MVCC可以为数据库解决以下问题:

  1. 在并发读取数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提供了数据库的并发读写性能
  2. 解决脏读、幻读、不可重复读等事务隔离问题,但是不能解决更新丢失的问题。

    MVCC实现原理时什么?

    实现原理主要依赖记录中的三个隐藏字段、undo log、read view来实现。

    隐藏字段

    每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID, DB_ROLL_PTR, DB_ROW_ID

    DB_TRX_ID

    6字节,最近修改事务ID,记录创建这条记录或者最后一次修改该记录的事务ID

    DB_ROLL_PTR

    7字节,回滚指针,指向这条记录的上一个版本,用于配合undolog,指向上一个旧版本

    DB_ROW_ID

    6字节,隐藏的主键,如果数据表没有主键,那么InnoDB会自动生成一个6字节的rowid,该记录的唯一隐式主键。

    undo log

    undo log 被成为回滚日志,表示在进行insert、update、delete操作的时候产生的方便回滚的日志。
    当进行insert操作时,产生的undo log只在事务回滚的时候需要,并且在事务提交之后被立即丢弃
    当进行update和delete的操作时,产生的undo log不仅仅在事务回滚的时候需要,在快照读的时候也需要,所以不能随便删除,只有在快照读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除(当数据发生更新或删除操作的时候都只是设置以下老记录的deleted_bit,并不是真正的将过时的记录删除,因为为了节省磁盘空间,innodb有专门的purge线程来清除deleted_bit = true的记录;如果某个记录的deleted_bit = true ,并且DB_TRX_ID相当于purge线程的read view 可见,那么这条记录一定可以被清除)

不同的事务或者相同的事务对同一记录的修改,会导致记录的undo log 生成一条记录版本线性表,即链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录。

read view

Read view是事务进行快照读操作时生产的读视图。在事务执行快照读的那一刻,会生成一个数据系统当前的快照,记录并维护系统当前活跃事务的id,事务的id是递增的。

其实read view的最大作用就是用来做可见性判断的,也就是说当某个事务在执行快照读的时候 ,对该记录创建一个read view的视图,把它当作条件区判断当前事务能看到那个版本的数据,有可能读取到的是最新的数据,也有可能读取的是当前记录的undo log的某个版本的数据。

read view遵循的可见性算法主要是将被修改的数据的最新记录的DB_TRX_ID(当前事务Id)取出来,与系统当前其他活跃事务的id对比,如果DB_TRX_ID跟read view的属性做了比较,不符合可见性,那么就通过DB_ROLL_PTR回滚指针去取出undo log中的DB_TRX_ID做比较,遍历链表中的DB_TRX_ID, 直到找到满足条件的DB_TRX_ID, 这个DB_TRX_ID所在的旧记录就是当前事务能看到的最新老版本的数据。

RC和RR级别下innodb的快照读有什么区别?

在RC级别下,是每个快照读都会生成并获取最新的read view
RR级别下,则是同一个事务中的第一个快照读才会创建read view, 之后快照读取得都是同一个read view.