MySQL多版本并发控制MVCC底层原理解析

目录
  • 1 事务并发中遇到的问题
    • 1.1 脏读
    • 1.2 不可重复读
    • 1.3 幻读
  • 2 隔离级别
  • 3 版本链
  • 4 ReadView
    • 4.1 ReadView 定义
    • 4.2 访问控制
    • 4.3 再谈隔离
      • 4.3.1 READ COMMITTED(读已提交)
      • 4.3.2 REPEATABLE READ(可重读)
  • 5 幻读
  • 6 总结

1 事务并发中遇到的问题

1.1 脏读

当一个事务读取到了另外一个事务修改但未提交的数据,被称为脏读。

1.2 不可重复读

当事务内相同的记录被检索两次,且两次得到的结果不同时,此现象称为不可重复读。

1.3 幻读

当一个事务同样的查询条件查询两次(多次),查出的条数不一致称为幻读。

2 隔离级别

我们上边介绍了几种并发事务执行过程中可能遇到的一些问题,这些问题也有轻重缓急之分,我们给这些问题按照严重性来排一下序:

脏读 > 不可重复读 > 幻读

SQL 标准中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的

问题,具体情况如下:

  • READ UNCOMMITTED:未提交读。
  • READ COMMITTED:已提交读。
  • REPEATABLE READ:可重复读。
  • SERIALIZABLE:可串行化

SQL 标准中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题,具体情况如下:

  • READ UNCOMMITTED 隔离级别下,可能发生脏读、不可重复读和幻读问题。
  • READ COMMITTED 隔离级别下,可能发生不可重复读和幻读问题,但是不可以发生脏读问题。
  • REPEATABLE READ 隔离级别下,可能发生幻读问题,但是不可以发生脏读和不可重复读的问题。
  • SERIALIZABLE 隔离级别下,各种问题都不可以发生。

3 版本链

我们知道,对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列(row_id 并不是必要的,我们创建的表中有主键或者非 NULL的 UNIQUE 键时都不会包含 row_id 列):

trx_id: 每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务 id 赋值给 trx_id 隐藏列。

roll_pointer: 每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo 日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。

假设插入该记录的事务 id 为 80的记录,那么此刻该条记录的示意图如下所示:

假设之后两个事务 id 分别为 100、200 的事务对这条记录进行 UPDATE 操作,操作流程如下:

Trx 100:

UPDATE t_people SET name = '关羽' WHERE number = 1;
UPDATE t_people SET name = '张飞' WHERE number = 1;

Trx 200:

UPDATE t_people SET name = '赵云' WHERE number = 1;
UPDATE t_people SET name = '诸葛亮' WHERE number = 1;

每次对记录进行改动,都会记录一条 undo 日志,每条 undo 日志也都有一个 roll_pointer 属性(INSERT 操作对应的 undo 日志没有该属性,因为该记录并没有更早的版本),可以将这些 undo 日志都连起来,串成一个链表,所以现在的情况就像下图一样:

对该记录每次更新后,都会将旧值放到一条 undo 日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被 roll_pointer 属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务 id。于是可以利用这个记录的版本链来控制并发事务访问相同记录的行为,那么这种机制就被称之为多版本并发控制(Mulit-Version Concurrency Control MVCC)

4 ReadView

4.1 ReadView 定义

InnoDB 提出了一个 ReadView 的概念,这个 ReadView 中主要包含 4个比较重要的内容:

  • (1) m_ids:表示在生成 ReadView 时当前系统中 活跃 的读写事务的事务 id 列表。
  • (2) min_trx_id: 表示在生成 ReadView 时当前系统中活跃的读写事务中最小的事务 id,也就是 m_ids 中的最小值。
  • (3) max_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。
  • (4) creator_trx_id:表示生成该 ReadView 的事务的事务 id。

4.2 访问控制

有了这个 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之间(min_trx_id < trx_id < max_trx_id),那就需要判断一下 trx_id 属性值是不是在m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
  • (5) 如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。

4.3 再谈隔离

对于使用 READ UNCOMMITTED 隔离级别的事务来说,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了。

对于使用 SERIALIZABLE 隔离级别的事务来说,InnoDB 使用加锁的方式来访问记录。

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

4.3.1 READ COMMITTED(读已提交)

读已提交,每次读取数据前都生成一个 ReadView。

假设现在有一个使用 READ COMMITTED 隔离级别的事务开始执行:

详解查询:

#使用 READ COMMITTED 隔离级别的事务
#Transaction 100、200未提交,得到的列 name 的值为 刘备
SELECT name FROM t_people WHERE number = 1;  

这个 SELECET 的执行过程如下:

  • (1) 在执行 SELECT 语句时会先生成一个 ReadView,ReadView 的 m_ids 列表的内容就是[100, 200],min_trx_id 为 100,max_trx_id 为 201,creator_trx_id 为 0。
  • (2) 然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列 name 的内容是诸葛亮,该版本的 trx_id 值为 200,在 m_ids 列表内,所以不符合可见性要求。(如果被访问版本的 trx_id 属性值在 ReadView 的 min_trx_id 和 max_trx_id之间,就需要判断一下 trx_id 属性值是不是在m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问 ),根据 roll_pointer 跳到下一个版本。
  • (3) 诸葛亮下一个版本的列name的内容是赵云,该版本的trx_id值也为200,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
  • (4) 赵云下一个版本的列name的内容是张飞,该版本的trx_id值也为100,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
  • (5) 张飞下一个版本的列name的内容是关羽,该版本的trx_id值也为100,也在m_ids列表内,所以也不符合要求,继续跳到下一个版本。
  • (6) 关羽下一个版本是刘备,该版本的trx_id值为80,小于ReadView 中的 min_trx_id 值,所以这个版本是符合要求的。

不可重复读: 100事务、200事务开启读取到name都为刘备。当100事务提交时,由于是读已提交事务隔离级别,每次读取都会创建ReadView,200事务读取时,创建生成的ReadView m_ids 为 [200],这时根据读取规则读取到的name就为张飞。

# 使用 READ COMMITTED 隔离级别的事务
BEGIN;
# SELECE1:Transaction 100、200 均未提交,得到name值为刘备
SELECT name  FROM t_people WHERE number = 1; 

# SELECE2:Transaction 100 提交,Transaction 200 未提交
#Transaction 200 事务查询,得到name值为张飞,发生不可重复读。
SELECT name  FROM teacher WHERE number = 1; 

4.3.2 REPEATABLE READ(可重读)

可重读,在第一次读取数据时生成一个 ReadView。

解决不可重复读: 100事务、200事务开启,创建ReadView,m_ids 为[100,200],读取到name都为刘备。当100事务提交时,由于是可重读事务隔离级别,只创建一次ReadView,m_ids 仍然是[100,200],这时根据读取规则读取到的name仍然是刘备。

5 幻读

当一个事务同样的查询条件查询两次(多次),查出的条数不一致称为幻读。

在 REPEATABLE READ 隔离级别下的事务 T1 先根据某个搜索条件读取到多条记录,然后事务 T2 插入一条符合相应搜索条件的记录并提交,然后事务 T1 再根据相同搜索条件执行查询。结果会是什么?按照 ReadView 中的比较规则,不管事务 T2 比事务 T1 是否先开启,事务 T1 都是看不到 T2 的提交的。但是,在 REPEATABLE READ 隔离级别下 InnoDB 中的 MVCC 可以很大程度地避免幻读现象,而不是完全禁止幻读。

#SELECT:快照读。update:当前读。
REPEATABLE READ 可以解决快照读幻读问题。解决不了当前读幻读的问题。

案例:

1 执行begin,执行select *。

2 开启另一个窗口 执行insert、select、commit。

3 回到原窗口执行查询

4 执行 update 、提交

5 查找

6 总结

从上边的描述中我们可以看出来,所谓的 MVCC(Multi-Version ConcurrencyControl ,多版本并发控制)指的就是在使用 READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的 SELECT 操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。

READ COMMITTD、REPEATABLE READ 这两个隔离级别的一个很大不同就是,生成 ReadView 的时机不同,READ COMMITTD 在每一次进行普通 SELECT 操作前都会生成一个 ReadView,而 REPEATABLE READ 只在第一次进行普通 SELECT 操作前生成一个 ReadView,之后的查询操作都重复使用这个 ReadView 就好了,从而基本上可以避免幻读现象。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • MySQL多版本并发控制MVCC深入学习

    MVCC MVCC(Multi-Version Concurrency Control),即多版本并发控制.是 innodb 实现事务并发与回滚的重要功能.锁机制可以控制并发操作,但是其系统开销较大,而MVCC可以在大多数情况下代替行级锁,使用MVCC,能降低其系统开销. 具体实现是在数据库的每一行中,额外添加三个字段: DB_TRX_ID : 记录插入或更新该行的最后一个事务的事务ID DB_ROLL_PTR : 指向改行对应undolog 的指针 DB_ROW_ID : 单调递增的ID,他就

  • 浅析MySQL - MVCC

    版本链 在InnoDB引擎表中,他们的聚簇索引记录中有两个隐藏列: trx_id:用来存储对数据进行修改时的事务id roll_pointer:每次对哪条聚簇索引记录有修改的时候,就会把老版本写入undo日志中.这个roll_pointer就是存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个版本的记录信息. id name trx_id roll_pointer 1 小明 50 0x00af 例如目前有个trx_id是60的事务正执行如下语句: update table

  • Mysql MVCC机制原理详解

    什么是MVCC MVCC,全称Multi-Version Concurrency Control,即多版本并发控制.MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存. 我们知道,一般情况下我们使用mysql数据库的时候使用的是Innodb存储引擎,Innodb存储引擎是支持事务的,那么当多线程同时执行事务的时候,可能会出现并发问题.这个时候需要一个能够控制并发的方法,MVCC就起到了这个作用. Mysql的锁和事务隔离级别 在理解MVCC机制

  • mysql的MVCC多版本并发控制的实现

    1 什么是MVCC MVCC全称是: Multiversion concurrency control,多版本并发控制,提供并发访问数据库时,对事务内读取的到的内存做处理,用来避免写操作堵塞读操作的并发问题. 举个例子,程序员A正在读数据库中某些内容,而程序员B正在给这些内容做修改(假设是在一个事务内修改,大概持续10s左右),A在这10s内 则可能看到一个不一致的数据,在B没有提交前,如何让A能够一直读到的数据都是一致的呢? 有几种处理方法,第一种: 基于锁的并发控制,程序员B开始修改数据时,

  • mysql多版本并发控制MVCC的实现

    事务隔离级别设置 set global transaction isolation level read committed; //全局的 set session transaction isolation level read committed; //当前会话 修改事务提交方式(是否自动提交,mysql默认自动提交) SET AUTOCOMMIT = 1; //自动提交,为0手动提交 不同数据库引擎MVCC模式各不相同,典型有乐观和悲观并发控制. innodb 说明: InnoDB的MVCC

  • MySQL多版本并发控制MVCC底层原理解析

    目录 1 事务并发中遇到的问题 1.1 脏读 1.2 不可重复读 1.3 幻读 2 隔离级别 3 版本链 4 ReadView 4.1 ReadView 定义 4.2 访问控制 4.3 再谈隔离 4.3.1 READ COMMITTED(读已提交) 4.3.2 REPEATABLE READ(可重读) 5 幻读 6 总结 1 事务并发中遇到的问题 1.1 脏读 当一个事务读取到了另外一个事务修改但未提交的数据,被称为脏读. 1.2 不可重复读 当事务内相同的记录被检索两次,且两次得到的结果不同时

  • MySQL多版本并发控制MVCC详解

    目录 1.什么是MVCC 2快照读与当前读 2.1 快照读 2.2当前读 3.复习 3.1 再谈隔离级别 3.2 隐藏字段.Undo Log版本链 4.MVCC实现原理之ReadView 4.1什么是ReadView 4.2 设计思路 4.3 ReadView的规则 5.举例说明 5.1 READ COMMITTED 5.2 REPEATABLE READ 5.3 如何解决幻读 6.总结 1.什么是MVCC MVCC (Multiversion Concurrency Control),多版本并

  • MySQL的多版本并发控制MVCC的实现

    目录 什么是MVCC MVCC的实现 MVCC 有没有解决幻读? 什么是MVCC MVCC就是多版本并发控制. MySQL的事务型存储引擎通过多版本并发控制(MVCC)来提升并发性能. 可以认为MVCC是行级锁的一个变种,但是它在大多数情况下避免了加锁操作,同时实现非阻塞的读操作,因此开销更低. MVCC是通过保存数据在某个时间点的快照来实现的,核心思想就是保存数据的历史版本,通过对数据行的多个版本管理来实现数据库的并发控制. 这样我们就可以通过比较版本号决定数据是否显示出来,读取数据的时候不需

  • 详解MySQL多版本并发控制机制(MVCC)源码

    目录 一.前言 二.MVCC(多版本并发控制机制) 2.1.Repeatable Read 2.2.Read Commit 2.3.MVCC的优势 三.MVCC(实现机制) 3.1.select运行栈 3.2.read_view的创建过程 3.3.行版本可见性 3.4.undolog搜索可见版本的过程 3.5.read_view创建时机再讨论 四.MVCC和锁的同时作用导致的一些现象 五.总结 一.前言 作为一个数据库爱好者,自己动手写过简单的SQL解析器以及存储引擎,但感觉还是不够过瘾.<<

  • Mysql InnoDB多版本并发控制MVCC详解

    目录 一丶为什么需要事务隔离级别 1.实现事务隔离的方式:串行执行 2.实现事务隔离的方式:可串行执行 二丶并发事务执行的问题:脏写,脏读,不可重复读,幻读 1.脏写 2.脏读 3.不可重复读 4.幻读 三丶隔离级别 1.Read UnCommitted 读未提交 2.Read Committed 读已提交 3.Repeatable Read 可重复读 4.Serializable 可串行化 四丶Mysql设置隔离级别 1.设置全局隔离级别 2.设置会话隔离级别 3.设置下一个事务的隔离级别 4

  • mysql事务和隔离级别底层原理浅析

    目录 前言 一.事务底层原理浅析 原子性: 持久性 隔离性: 一致性: 二.隔离级别底层原理浅析 三.总结 前言 首先回顾一下什么是事务,事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作:这些操作作为一个整体一起向系统提交,要么都执行.要么都不执行:事务是一组不可再分割的操作集合(工作逻辑单元). 事务的特性: 原子性(Atomicity):原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚.一致性(Consistency):事务执行的结果必须是使数据库从一个一致性

  • Activiti工作流学习笔记之自动生成28张数据库表的底层原理解析

    网上关于工作流引擎Activiti生成表的机制大多仅限于四种策略模式,但其底层是如何实现的,相关文章还是比较少,因此,觉得撸一撸其生成表机制的底层原理. 我接触工作流引擎Activiti已有两年之久,但一直都只限于熟悉其各类API的使用,对底层的实现,则存在较大的盲区. Activiti这个开源框架在设计上,其实存在不少值得学习和思考的地方,例如,框架用到以命令模式.责任链模式.模板模式等优秀的设计模式来进行框架的设计. 故而,是值得好好研究下Activiti这个框架的底层实现. 我在工作当中现

  • 从云数据迁移服务看MySQL大表抽取模式的原理解析

    摘要:MySQL JDBC抽取到底应该采用什么样的方式,且听小编给你娓娓道来. 小编最近在云上的一个迁移项目中被MySQL抽取模式折磨的很惨.一开始爆内存被客户怼,再后来迁移效率低下再被怼.MySQL JDBC抽取到底应该采用什么样的方式,且听小编给你娓娓道来. 1.1 Java-JDBC通信原理 JDBC与数据库之间的通信是通过socket完,大致流程如下图所示.Mysql Server ->内核Socket Buffer -> 客户端Socket Buffer ->JDBC所在的JV

随机推荐