天天看点

MySQL的MVCC及实现原理

目录

一、前提概要

1. 什么是MVCC

2. 什么是当前读和快照读

3. 当前读,快照读和MVCC的关系

4. MVCC带来的好处是

二、MVCC的实现原理

三、Read View(读视图)

四、RC,RR级别下的InnoDB快照读有什么不同?

MySql学习专栏

1. MySQL基础架构详解

2. MySQL索引底层数据结构与算法

3. MySQL5.7开启binlog日志,及数据恢复简单示例

4. MySQL日志模块

5. MySQL的MVCC及实现原理

6. MySQL索引优化

7. MySQL——通过EXPLAIN分析SQL的执行计划

8. MySQL执行语句性能优化

9. MySQL中的关联查询(内连接,外连接,自连接)

10. MySQL中复杂的增删改语句

11. 面试必问的 MySQL,你懂了吗?

一、前提概要

1. 什么是MVCC

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

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

2. 什么是当前读和快照读

在学习MVCC多版本并发控制之前,我们必须先了解一下,什么是MySQL InnoDB下的当前读和快照读?

    当前读

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

    快照读

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

    说白了MVCC就是为了实现读(select)-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现。

3. 当前读,快照读和MVCC的关系

    准确的说,MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突” 这么一个概念。仅仅是一个理想概念

    而在MySQL中,实现这么一个MVCC理想概念,我们就需要MySQL提供具体的功能去实现它,而快照读就是MySQL为我们实现MVCC理想模型的其中一个具体非阻塞读功能。而相对而言,当前读就是悲观锁的具体功能实现。

    要说的再细致一些,快照读本身也是一个抽象概念,再深入研究。MVCC模型在MySQL中的具体实现则是由 3个隐式字段,undo日志 ,Read View 等去完成的,具体可以看下面的MVCC实现原理

    MVCC能解决什么问题,好处是?

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

    读-读:不存在任何问题,也不需要并发控制

    读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读

    写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

4. MVCC带来的好处是

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

    所以MVCC可以为数据库解决以下问题:

        在并发读写数据库时,可以做到在读(select)操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能

        同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

        总之,MVCC就是因为大牛们,不满意只让数据库采用悲观锁这样性能不佳的形式去解决读-写冲突问题,而提出的解决方案,所以在数据库中,因为有了MVCC,所以我们可以形成两个组合:

        MVCC + 悲观锁

        MVCC解决读写冲突,悲观锁解决写写冲突

        MVCC + 乐观锁

        MVCC解决读写冲突,乐观锁解决写写冲突

        这种组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题

二、MVCC的实现原理

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

      隐式字段

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

  •        DB_TRX_ID

            6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID

  •        DB_ROLL_PTR

             7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)

  •        DB_ROW_ID

             6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚集索引

MySQL的MVCC及实现原理

如上图☝,DB_ROW_ID是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本

undo log日志主要分为两种

        insert undo log

            代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃

        update undo log

            事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读(select,当读的过程中有写的事务开始和提交,会造成读数据的脏读、不可重复读、幻读等)时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除。

purge

        从前面的分析可以看出,为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的deleted_bit,并不真正将过时的记录删除。

为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view);如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。

对MVCC有帮助的实质是update undo log ,undo log实际上就是存在rollback segment中旧记录链,它的执行流程如下:

一、 比如一个有个事务插入person表插入了一条新记录,记录如下,name为Jerry, age为24岁,隐式主键是1,事务ID和回滚指针,我们假设为NULL

MySQL的MVCC及实现原理

二、 现在来了一个事务1对该记录的name做出了修改,改为Tom

    在事务1修改该行(记录)数据时,数据库会先对该行加排他锁

然后把该行数据拷贝到undo log中,作为旧记录,既在undo log中有当前行的拷贝副本

拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的事务ID为当前事务1的ID, 我们默认从1开始,之后递增,回滚指针指向拷贝到undo log的副本记录,既表示我的上一个版本就是它

事务提交后,释放锁

MySQL的MVCC及实现原理

三、 又来了个事务2修改person表的同一个记录,将age修改为30岁

        在事务2修改该行数据时,数据库也先为该行加锁

然后把该行数据拷贝到undo log中,作为旧记录,发现该行记录已经有undo log了,那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面

修改该行age为30岁,并且修改隐藏字段的事务ID为当前事务2的ID, 那就是2,回滚指针指向刚刚拷贝到undo log的副本记录

事务提交,释放锁

MySQL的MVCC及实现原理

    从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既事务链,undo log的链首就是最新的旧记录,链尾就是最早的旧记录。

三、Read View(读视图)

        什么是Read View,说白了Read View就是事务进行快照读(select * from)操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成事务系统当前的一个快照,记录并维护系统当前活跃事务(未提交事务)的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)。

所以我们知道 Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

ReadView中主要包含4个比较重要的内容:

read view中活跃就是指未提交的事务

        1. m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。

        2. min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小 值。

        3. max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。

        4. creator_trx_id:表示生成该ReadView的快照读操作产生的事务id。

    注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1, 2, 3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时, m_ids就包括1和2, min_trx_id的值就是1,max_trx_id的值就是4。

基于RR可重复读隔离级别,实现的基本原理

        在select读数据的过程中,m_ids首次发现未提交的事务信息不会因在查找过程中其他事务id提交而把该事务id排除在外,直至查询到该事务链中最后提交的事务

读已提交的隔离级别

        m_ids:保存事务系统中的活跃的事务id,基于m_ids中的id信息在事务链中直到查找到非活跃的事务id(已提交的事务,不管事务提交是否在read view生成后),此时就认为是该事务id查询的信息

RC隔离级别是在执行sql时生成read view,RR隔离级别是在事务开始生成read view

有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:

  1.        如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
  2.        如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
  3.       如果被访问版本的trx_id属性值大于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
  4.        如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下 trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃 的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

四、RC,RR级别下的InnoDB快照读有什么不同

正是

Read View

生成时机的不同,从而造成RC,RR级别下快照读的结果的不同

  • 在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;
  • 即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见
  • 而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因

总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。

参考文档

1.https://blog.csdn.net/SnailMann/article/details/94724197