mvcc介绍

MVCC是数据库提供并发访问控制的一种技术。其核心理念是数据快照,不同的事务访问不同版本的数据快照,从而实现不同的事务隔离级别。虽然是说具有多个版本的数据快照,但这并不意味着数据库必须拷贝数据,保存多份数据文件,这样会浪费大量的存储空间。mysql的InnoDB通过事务的undo日志巧妙地实现了多版本的数据快照。与基于锁的并发控制 Lock-Based Concurrency Control (LBCC)相比,MVCC最大的好处:读不加锁,读写不冲突。

InnoDB的MVCC是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的事务ID,一个保存了行的回滚指针。每开始一个新的事务,都会自动递增产生一个新的事务id。

MVCC只在REPEATABLE READ和READ COMMITIED两个隔离级别下工作。其他两个隔离级别都和MVCC不兼容 ,因为READ UNCOMMITIED总是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE则会对所有读取的行都加锁。

MVCC 在mysql 中的实现依赖的是undo log与read view。

undo log

为了更好的支持并发,InnoDB的多版本一致性读是采用了基于回滚段的的方式。另外,对于更新和删除操作,InnoDB并不是真正的删除原来的记录,而是设置记录的delete mark为1。因此为了解决数据Page和Undo Log膨胀的问题,需要引入purge机制进行回收。Undo log保存了记录修改前的镜像。在InnoDB存储引擎中,undo log分为:insert undo log 与 update undo log。

insert undo log是指在insert操作中产生的undo log。由于insert操作的记录,只是对本事务可见,其他事务不可见,所以undo log可以在事务提交后直接删除,而不需要purge。
update undo log是指在delete和update操作中产生的undo log。该undo log会被后续用于MVCC当中,因此不能提交的时候删除。提交后会放入undo log的链表,等待purge线程进行最后的删除。

如下图所示(第一次修改):
当事务2使用UPDATE语句修改该行数据时,会首先使用排他锁锁定该行,将该行当前的值复制到undo log中,然后再真正地修改当前行的值,最后填写事务ID,使用回滚指针指向undo log中修改前的行。

 ReadView

对于使用 READ UNCOMMITTED 隔离级别的事务来说,直接读取记录的最新版本就好了。对于使用SERIALIZABLE 隔离级别的事务来说,使用加锁的方式来访问记录。对于使用 READ COMMITTED 和REPEATABLE READ 隔离级别的事务来说,就需要用到我们上边所说的 版本链 了。核心问题就是需要判断一下版本链中的哪个版本是当前事务可见的。所以设计 InnoDB 的设计者提出了一个ReadView的概念,这个 ReadView 中主要包含当前系统中还有哪些活跃的读写事务,把它们的事务id放到一个列表中,我们把这个列表命名为m_ids,并确定三个变量的值:

m_up_limit_id:m_ids事务列表中的最小事务id,如果当前列表为空那么就等于m_low_limit_id。事务id的下限。

m_low_limit_id:系统中将要产生的下一个事务id的值。事务id的上限。

m_creator_trx_id:当前事务id,m_ids中不包含当前事务id

这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本(版本链中的版本)是否可见:
1)如果被访问版本的 trx_id 属性值小于 m_up_limit_id ,表明生成该版本的事务在生成 ReadView前已经提交,所以该版本可以被当前事务访问。
2)如果被访问版本的 trx_id 属性值等于 m_creator_trx_id 既当前事务id,可以被访问。
3)如果被访问版本的 trx_id 属性值大于等于 m_low_limit_id ,在生成 ReadView 后才生成,所以该版本不可以被当前事务访问。
4)如果被访问版本的 trx_id 属性值在 m_up_limit_id 和 m_low_limit_id 之间,那就需要判断一下 trx_id 属性值是不是在 m_ids 列表中。
如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;
如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问

在 MySQL 中, READ COMMITTED 和 REPEATABLE READ 隔离级别的的一个非常大的区别就是它们生成ReadView 的时机不同,如下。
1.READ COMMITTED

每次读取数据前都生成一个ReadView

数据库中的记录是id为1,c为刘备,该行记录的版本是80
# Transaction 100
BEGIN;
UPDATE t SET c = '关羽' WHERE id = 1;
UPDATE t SET c = '张飞' WHERE id = 1;
select c from t where id =1时
在执行SELECT语句时会先生成一个 ReadView,ReadView 的 m_ids 列表的内容就是 [100]
然后从版本链中挑选可见的记录,最新版本的列 c 的内容是 '张飞' ,该版本的trx_id值为100,
在 m_ids 列表内,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本。
下一个版本的列 c 的内容是 '关羽' ,该版本的 trx_id 值也为 100 ,也在m_ids 列表内,所以也
不符合要求,继续跳到下一个版本。
下一个版本的列c的内容是'刘备',该版本的trx_id值为80,小于m_ids列表中最小的事务id 100,
所以这个版本是符合要求的,最后返回给用户的版本就是这条列c为'刘备'的记录。

# Transaction 200
BEGIN;
UPDATE t SET c = '赵云' WHERE id = 1;
UPDATE t SET c = '诸葛亮' WHERE id = 1;

执行如下操作:
select c from t where id =1时
在执行SELECT语句时会先生成一个 ReadView,ReadView 的 m_ids 列表的内容就是 [100,200],查找的结果仍然为刘备
此时可以看出,Transaction 100与200没有提交时,查询的结果仍然是刘备的,这就达到了RC隔离级别下的mvcc控制的效果

Transaction 100 事务提交
select c from t where id =1时
在执行SELECT语句时会重新生成一个 ReadView,ReadView 的 m_ids 列表的内容是 [200],查找的结果为张飞。事务1提交了,才查询到了最新的记录。

2.READ REPEATABLE

在事务开始后第一次读取数据时生成一个ReadVie
数据库中的记录是id为1,c为刘备,该行记录的版本是80
# Transaction 100
BEGIN;
UPDATE t SET c = '关羽' WHERE id = 1;
UPDATE t SET c = '张飞' WHERE id = 1;
select c from t where id =1时
在执行SELECT语句时会先生成一个 ReadView,ReadView 的m_ids列表的内容是 [100]
然后从版本链中挑选可见的记录,最新版本的列c的内容是'张飞',该版本的trx_id值为100,
在m_ids列表内,所以不符合可见性要求,根据 roll_pointer 跳到下一个版本。
下一个版本的列c的内容是'关羽' ,该版本的trx_id值也为100,也在m_ids 列表内,所以也
不符合要求,继续跳到下一个版本。
下一个版本的列c的内容是'刘备',该版本的trx_id值为80,小于m_ids列表中最小的事务id 100,
所以这个版本是符合要求的,最后返回给用户的版本就是这条列c为'刘备'的记录。

# Transaction 200
BEGIN;
UPDATE t SET c = '赵云' WHERE id = 1;
UPDATE t SET c = '诸葛亮' WHERE id = 1;

执行如下操作:
select c from t where id =1时
在执行SELECT语句时会第一次产生的ReadView 的 m_ids列表的内容就是[100],查找的结果仍然为刘备

Transaction 100 事务提交,
select c from t where id =1时
之前的ReadView的m_ids 列表的内容仍然是[100],查找的结果仍然为刘备,
这就可以看出,Transaction 100第一次查询时,在READ REPEATABLE隔离级别下,产生的ReadView的m_ids列表的内容是[100],尽管
之后Transaction 100 事务提交了,但是ReadView的m_ids列表没变,所以查询的结果仍然为刘备,达到了可重复读的效果。



 

每次读取数据前都生成一个ReadView

(三) Mysql 之MVCC的更多相关文章

  1. java面试一日一题:讲对mysql的MVCC的理解

    问题:请讲下对mysql中MVCC的理解 分析:这个问题要回答的是对MVCC的理解,以及MVCC解决了什么问题这几个方面入手. 回答要点: 主要从以下几点去考虑, 1.什么是MVCC? 2.MVCC用 ...

  2. mysql的mvcc(多版本并发控制)

    mysql的mvcc(多版本并发控制) 我们知道,mysql的innodb采用的是行锁,而且采用了多版本并发控制来提高读操作的性能. 什么是多版本并发控制呢 ?其实就是在每一行记录的后面增加两个隐藏列 ...

  3. {MySQL数据库初识}一 数据库概述 二 MySQL介绍 三 MySQL的下载安装、简单应用及目录介绍 四 root用户密码设置及忘记密码的解决方案 五 修改字符集编码 六 初识sql语句

    MySQL数据库初识 MySQL数据库 本节目录 一 数据库概述 二 MySQL介绍 三 MySQL的下载安装.简单应用及目录介绍 四 root用户密码设置及忘记密码的解决方案 五 修改字符集编码 六 ...

  4. 【数据库】悲观锁与乐观锁与MySQL的MVCC实现简述

    悲观锁 悲观锁,就是一种悲观心态的锁,每次访问数据时都会锁定数据: 乐观锁 乐观锁,就是一种乐观心态的锁,每次访问数据时并不锁定数据,期待数据并没作修改,如果数据没被修改则作具体的业务 应用程序上使用 ...

  5. MySQL InnoDB MVCC

    MySQL 原理篇 MySQL 索引机制 MySQL 体系结构及存储引擎 MySQL 语句执行过程详解 MySQL 执行计划详解 MySQL InnoDB 缓冲池 MySQL InnoDB 事务 My ...

  6. Mysql中MVCC的使用及原理详解

      准备 测试环境:Mysql 5.7.20-log 数据库默认隔离级别:RR(Repeatable Read,可重复读),MVCC主要适用于Mysql的RC,RR隔离级别 创建一张存储引擎为test ...

  7. 【MySQL】面试官:谈谈你对Mysql的MVCC的理解?

    MVCC(Mutil-Version Concurrency Control),就是多版本并发控制.MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问. 在Mysql的In ...

  8. mysql 之mvcc多版本控制

    MVCC是multiversion concurrency control的缩写,提供MySQL事物隔离级别下无锁读,例如一个事物在执行update等修改数据的sql,并未提交时其他事物进行数据读取是 ...

  9. MySQL InnoDB MVCC深度分析

    关于MySQL的InnoDB的MVCC原理,很多朋友都能说个大概: 每行记录都含有两个隐藏列,分别是记录的创建时间与删除时间 每次开启事务都会产生一个全局自增ID 在RR隔离级别下 INSERT -& ...

  10. 《Mysql - 事务 MVCC》

    一:前言 - 前面通过 <Mysql 事务 - 隔离> 的学习,知道了事务的实现,是根据 获取一致性视图 来实现的. 二:那么,什么时候会获取到一致性视图呢? - 例如:有三个事务,启动的 ...

随机推荐

  1. 你不知道的Map家族中的那些冷门容器

    概述 本篇文章主要讲解下Map家族中3个相对冷门的容器,分别是WeakHashMap.EnumMap.IdentityHashMap, 想必大家在平时的工作中也很少用到,或者压根不知道他们的特性以及适 ...

  2. python 之excel文件读取封装

    import os import xlrd PATH = lambda p: os.path.abspath( os.path.join(os.path.dirname(__file__), p) ) ...

  3. TS学习笔记

    类型 类型 例子 描述 number 1,2,-2 任意数字 string 'hi',"hi" 任意字符串 boolean true,false 布尔值或者true false 字 ...

  4. Solon Java Framework v1.12.0 发布

    一个更现代感的 Java 应用开发框架:更快.更小.更自由.没有 Spring,没有 Servlet,没有 JavaEE:独立的轻量生态.主框架仅 0.1 MB. @Controller public ...

  5. [常用工具] OpenCV_contrib库在windows下编译使用指南

    本文主要讲述opencv及opencv_contrib库在windows下基于vs2017编译安装指南.所用OpenCV版本为OpenCV4.4,编译平台为vs2017. 文章目录 1 下载 2 编译 ...

  6. Linux c 检测当前网卡是否已经启动

    思路: 1.socket 建立一个数据报套接字. 2.定义一个struct ifreq ifr 结构体.将网络名称如"eth0" 赋值给ifr结构体的ifr.ifr_name. 3 ...

  7. 包子类&包子铺类-吃货类&测试类

    包子类&包子铺类 资源类:包子类设置包子的属性皮陷包子的状态:有true,没有false package Demo01.WaitAndNotify; /** * 资源类:包子类设置包子的属性 ...

  8. 微信公众号签名错误(invalid signature)的问题排查

    之前写好的代码,好多项目一直在用没啥问题,今天做新项目,在调用的时候,wx.config提示签名错误(invalid signature),这搞得相当郁闷,没办法,只能重新一点一点调试. 按照官方的说 ...

  9. Kubernetes(k8s)控制器(二):DaemonSet

    目录 一.系统环境 二.前言 三.DaemonSet 概览 四.创建DaemonSet 4.1 创建daemonset 让其在k8s集群所有worker节点运行pod 4.2 创建daemonset让 ...

  10. Vue 07 js方法Object.defineProperty

    1 描述 该方法允许精确地添加或修改对象的属性.可以对已有的属性进行获取及修改,也可以添加新的属性并进行操作. 2 方法参数 Object.defineProperty(操作的对象,添加的属性的名称, ...