1. 什么是MVCC

MVCC全称是Multi-Version Concurrency Control(多版本并发控制),是一种并发控制的方法,通过维护一个数据的多个版本,减少读写操作的冲突。

如果没有MVCC,想要实现同一条数据的并发读写,还要保证数据的安全性,就需要操作数据的时候加读锁和写锁,这样就降低了数据库的并发性能。

有了MVCC,就相当于把同一份数据生成了多个版本,在操作的开始各生成一个快照,读写操作互不影响。无需加锁,也实现数据的安全性和事务的隔离性。

事务的四大特性中隔离性就是基于MVCC实现的。

说MVCC的实现原理之前,先说一下事务的隔离级别。

2. 事务的隔离级别

说隔离级别之前,先说一下并发事务产生的问题

脏读: 一个事务读到其他事务未提交的数据。

不可重复读: 相同的查询条件,多次查询到的结果不一致,即读到其他事务提交后的数据。

幻读: 相同的查询条件,多次查询到的结果不一致,即读到其他事务提交后的数据。

不可重复读与幻读的区别是: 不可重复读是读到了其他事务执行update、delete后的数据,而幻读是读到其他事务执行insert后的数据。

再说一下事务的四大隔离级别:

Read UnCommitted(读未提交): 读到其他事务未提交的数据,会出现脏读、不可重复读、幻读。

Read Committed(读已提交): 读到其他事务已提交的数据,解决了脏读,会出现不可重复读、幻读。

Repeatable Read(可重复读): 相同的条件,多次读取到的结果一致。解决了脏读、不可重复读,会出现幻读。

Serializable(串行化): 所有事务串行执行,解决了脏读、不可重复读、幻读。

隔离级别 脏读 不可重复读 幻读
读未提交
读已提交 不会
可重复读 不会 不会
串行化 不会 不会 不会

MVCC只在Read CommittedRepeatable Read两个隔离级别下起作用,因为Read UnCommitted隔离级别下,读写都不加锁,Serializable隔离级别下,读写都加锁,也就不需要MVCC了。

再谈一下Undo log日志。

3. Undo Log(回滚日志)

Undo Log记录的是逻辑日志,也就是SQL语句。

比如:当我们执行一条insert语句时,Undo Log就记录一条相反的delete语句。

作用:

  1. 回滚事务时,恢复到修改前的数据。

  2. 实现 MVCC

事务四大特性中原子性也是基于Undo Log实现的。

下面开始谈一下MVCC的实现原理。

4. MVCC的实现原理

4.1 当前读和快照读

先普及一下什么是当前读和快照读。

当前读: 读取数据的最新版本,并对数据进行加锁。

例如:insert、update、delete、select for update、 select lock in share mode。

快照读: 读取数据的历史版本,不对数据加锁。

例如:select

MVCC是基于Undo Log、隐藏字段、Read View(读视图)实现的。

4.2 隐藏字段

先说一下MySQL的隐藏字段,当我们创建一张表时,InnoDB引擎会增加2个隐藏字段。

DB_TRX_ID(最近一次提交事务的ID):修改表数据时,都会提交事务,每个事务都有一个唯一的ID,这个字段就记录了最近一次提交事务的ID。

DB_ROLL_PTR(上个版本的地址):修改表数据时,旧版本的数据都会被记录到Undo Log日志中,每个版本的数据都有一个版本地址,这个字段记录的就是上个版本的地址。

4.3 版本链

当我们第一次往用户表插入一条记录时,表数据和隐藏字段的值是下面这样的:

insert into user (name,age) values ('一灯',1);

事务ID(DB_TRX_ID)是1,上个版本地址(DB_ROLL_PTR)是null。

第二次提交事务,把用户年龄加1。

update user set age=age+1 where id=1;

事务ID变成2,上个版本地址指向Undo Log中的记录。

第三次提交事务,再把用户年龄加1。

update user set age=age+1 where id=1;

事务ID变成3,上个版本地址指向Undo Log中事务ID为2的记录。

这样表记录和Undo Log历史数据就组成了一个版本链。

4.4 Read View(读视图)

在事务中,执行SQL查询,就会生成一个读视图,是用来保证数据的可见性,即读到Undo Log中哪个版本的数据。

快照读一般是读取的历史版本的读视图,当前图会生成一个最新版本的读视图。

读视图是基于下面几个字段实现的:

m_ids :当前系统中活跃的事务ID集合,即未提交的事务。

min_trx_id :m_ids中最小的ID

max_trx_id :下一个要分配的事务ID

creator_trx_id: 当前事务ID

读视图决定当前事务能读到哪个版本的数据,从表记录到Undo Log历史数据的版本链,依次匹配,满足哪个版本的匹配规则,就能读到哪个版本的数据,一旦匹配成功就不再往下匹配。

数据可见性规则:

  1. DB_TRX_ID = creator_trx_id

    如果这个版本数据的事务ID等于当前事务ID,表示数据记录的最后一次操作的事务就是当前事务,当前读视图可以读到这个版本的数据。
  2. DB_TRX_ID < min_trx_id

    如果这个版本数据的事务ID小于所有活跃事务ID,表示这个版本的数据不再被事务使用,即事务已提交,当前读视图可以读到这个版本的数据。
  3. DB_TRX_ID >= max_trx_id

    如果这个版本数据的事务ID大于等于下一个要分配的事务ID,表示有新事务更新了这个版本的数据,这种情况下,当前读视图不可以读到这个版本的数据。
  4. min_trx_id <= DB_TRX_ID < max_trx_id

    如果这个版本数据的事务ID在当前系统中活跃的事务ID集合(m_ids)里面,表示这个版本的数据被其他事务更新过,当前读视图不可以读到这个版本的数据。

    如果这个版本数据的事务ID不在当前系统中活跃的事务ID集合(m_ids)里面,表示是在其他事务提交后创建的读视图,当前读视图可以读到这个版本的数据。

5. 不同隔离级别下可见性分析

在不同的事务隔离级别下,生成读视图的规则不同:

  • READ COMMITTED(读已提交) :在事务中每一次执行快照读时都生成一个读视图,每个读视图中四个字段的值都是不同的。
  • REPEATABLE READ(可重复读):仅在事务中第一次执行快照读时生成读视图,后续复用这个读视图。

5.1 READ COMMITTED(读已提交)

设置MySQL隔离级别为读已提交:

SET session TRANSACTION ISOLATION LEVEL READ COMMITTED;

执行两个事务,验证一下:

事务1第一次查询时,会生成一个读视图,读视图的各个属性如下:

属性
m_ids 1,2
min_limit_id 1
max_limit_id 3
creator_trx_id 1

可见的版本链数据是:

符号规则 DB_TRX_ID = creator_trx_id = 1,可以看到当前版本的数据。

事务1第二次查询时,会生成一个新的读视图,读视图的各个属性如下:

属性
m_ids 1
min_limit_id 1
max_limit_id 3
creator_trx_id 1

可见的版本链数据是:

符号规则 min_trx_id <= DB_TRX_ID < max_trx_id(1<=2<3),并且当前数据版本的事务ID不在当前系统中活跃的事务ID集合,可以看到当前版本的数据。

同一个事务内,相同的查询条件,查询到的数据不一致,查到了其他事务更新过的数据,也就是出现了不可重复读的情况。

再看一下,在可重复读隔离级别下,是怎么解决这个问题的。

5.2 REPEATABLE READ(可重复读)

设置MySQL隔离级别为可重复读:

SET session TRANSACTION ISOLATION LEVEL REPEATABLE READ;

执行两个事务,验证一下:

事务1第一次查询时,会生成一个读视图,读视图的各个属性如下:

属性
m_ids 1,2
min_limit_id 1
max_limit_id 3
creator_trx_id 1

可见的版本链数据是:

符号规则 DB_TRX_ID = creator_trx_id = 1,可以看到当前版本的数据。

事务1第二次查询时,会复用原有的读视图,读视图的各个属性如下:

属性
m_ids 1,2
min_limit_id 1
max_limit_id 3
creator_trx_id 1

可见的版本链数据是:

符号规则 min_trx_id <= DB_TRX_ID < max_trx_id(1<=2<3),并且当前数据版本的事务ID在当前系统中活跃的事务ID集合,所以是不可以看到当前版本的数据。

由此得知,可重复读隔离级别下,相同的查询条件,两次查询到的结果相同,也就是解决了可重复读的问题,是通过复用原有的读视图的方式解决的。

硬核解析MySQL的MVCC实现原理,面试官看了都直呼内行的更多相关文章

  1. 深入解析Mysql 主从同步延迟原理及解决方案

    MySQL的主从同步是一个很成熟的架构,优点为:①在从服务器可以执行查询工作(即我们常说的读功能),降低主服务器压力;②在从主服务器进行备份,避免备份期间影响主服务器服务;③当主服务器出现问题时,可以 ...

  2. 「MySQL高级篇」MySQL之MVCC实现原理&&事务隔离级别的实现

    大家好,我是melo,一名大三后台练习生,死去的MVCC突然开始拷打我! 引言 MVCC,非常顺口的一个词,翻译起来却不是特别顺口:多版本并发控制. 其中多版本是指什么呢?一条记录的多个版本. 并发控 ...

  3. 云栖干货回顾 | 云原生数据库POLARDB专场“硬核”解析

    POLARDB是阿里巴巴自主研发的云原生关系型数据库,目前兼容三种数据库引擎:MySQL.PostgreSQL.Oracle.POLARDB的计算能力最高可扩展至1000核以上,存储容量可达100TB ...

  4. 掌握这四大MySQL知识点,吊打面试官

    作为一名后端开发,MySQL的使用必不可少,合理的使用索引和索引调优是后端开发者必须掌握的技能之一. 在日常数据库的问题当中,不合理的使用索引占大部分. MySQL是大家工作上最常用的关系型数据库之一 ...

  5. 【MySQL】我这样分析MySQL中的事务,面试官对我刮目相看!!

    写在前面 相信大部分小伙伴在面试过程中,只会针对面试官提出的表面问题来进行回答.其实不然,面试官问的每一个问题都是经过深思熟虑的,面试的时间相对来说也是短暂的,面试官不可能在很短的时间内就对你非常了解 ...

  6. 20170103简单解析MySQL查询优化器工作原理

    转自博客http://www.cnblogs.com/hellohell/p/5718238.html 感谢楼主的贡献 查询优化器的任务是发现执行SQL查询的最佳方案.大多数查询优化器,包括MySQL ...

  7. 超硬核解析!Apache Hudi灵活的Payload机制

    Apache Hudi 的Payload是一种可扩展的数据处理机制,通过不同的Payload我们可以实现复杂场景的定制化数据写入方式,大大增加了数据处理的灵活性.Hudi Payload在写入和读取H ...

  8. 硬核剖析ThreadLocal源码,面试官看了直呼内行

    工作面试中经常遇到ThreadLocal,但是很多同学并不了解ThreadLocal实现原理,到底为什么会发生内存泄漏也是一知半解?今天一灯带你深入剖析ThreadLocal源码,总结ThreadLo ...

  9. 阿里P7面试官:请你简单说一下类加载机制的实现原理?

    面试题:类加载机制的原理 面试官考察点 考察目标: 了解面试者对JVM的理解,属于面试八股文系列. 考察范围: 工作3年以上. 技术背景知识 在回答这个问题之前,我们需要先了解一下什么是类加载机制? ...

随机推荐

  1. RocketMQ 集群的搭建部署 以及rocketmq-console-ng仪表台的安装部署

    在 RocketMQ 主要的组件如下. NameServerNameServer 集群,Topic 的路由注册中心,为客户端根据 Topic 提供路由服务,从而引导客户端向 Broker 发送消息.N ...

  2. P6622 信号传递 做题感想

    题目链接 前言 在这里分享两种的做法. 一种是我第一直觉的 模拟退火.(也就是骗分) 还有一种是看题解才搞懂的神仙折半搜索加上 dp . 模拟退火 众所周知,模拟退火 是我这种没脑子选手用来骗分的好算 ...

  3. JavaScript知识梳理

    JS内功修炼 专业术语 类,封装,继承, 专业术语 babel 块级作用域 函数 扩展对象的功能性 解构 set和map js的类 改进的数组功能 Promise与异步编程 代理和反射 用模块封装代码 ...

  4. 交替方向乘子法(Alternating Direction Multiplier Method,ADMM)

    交替方向乘子法(Alternating Direction Multiplier Method,ADMM)是一种求解具有可分结构的凸优化问题的重要方法,其最早由Gabay和Mercier于1967年提 ...

  5. Java数组和Arrays 类

    1.创建数组的三种方式: ①动态初始化:数组的初始化和数组元素的赋值操作分开进行 dataType[ ] arrayRefVar = new dataType [ arraySize ] ; Emp ...

  6. 静态代码块和数组工具类Arrays

    静态代码块 静态代码块:定义在成员位置,使用static修饰的代码块{ }. ~位置:类中方法外. ~执行:随着类的加载而执行且执行一次,优先于main方法和构造方法的执行 格式: public cl ...

  7. 如何快速体验OneOS

    随便逛了逛 之前有简单了解过OneOS,今天逛了下OneOS专区,发现官方终于也在宣传方面发力了啊,很多文章都非常专业,也有开发者在专区里面提问题了.也发现,部分开发者倒在了第一步,如何编译下载运行O ...

  8. [System.OutOfMemoryException] {函数求值已禁用,因为出现内存不足异常。

    [System.OutOfMemoryException] {函数求值已禁用,因为出现内存不足异常. StringBuilder 赋值的时候超过内存的大小,要即时去清空文本的值. 也可能是DataSe ...

  9. 聊一聊 C# 后台GC 到底是怎么回事?

    一:背景 写这一篇的目的主要是因为.NET领域内几本关于阐述GC方面的书,都是纯理论,所以懂得人自然懂,不懂得人也没法亲自验证,这一篇我就用 windbg + 源码 让大家眼见为实. 二:为什么要引入 ...

  10. elastsearch整合springboot

    文档地址: https://www.baeldung.com/elasticsearch-java