参考书籍《mysql是怎样运行的》

系列文章目录和关于我

一丶为什么需要事务隔离级别

mysql是一个客户端/服务断软件,对于同一个服务器来说,可以有多个客户端进行连接,每一个客户端进行连接之后就形成一个会话,每一个客户端都可以在自己的会话中向服务器发出请求语句,一个请求语句可能是某一个事务的一部分,服务器可以同时处理多个事务。

如果事务时一个接着一个进行,那么下一个事务是在上一个事务的一致性前提下进行的,就没用一致性的问题,但是事务是并发进行且可能访问到相同的数据这时候就会出现如下问题

可以看到AB最开始总和13元,最后AB总和18元,银行血亏五元,这显然违背了一致性——钱的总量不变。这就是并发情况下两个事务的影响,所以需要事务隔离让事务隔离的进行,互不干涉。

1.实现事务隔离的方式:串行执行

最简单直接的方式,同一时间只能有一个事务运行,这样必然不会有上述不一致的情况,但是大大降低了吞吐率并增加了事务的等待时间

2.实现事务隔离的方式:可串行执行

并发事务之所以出现不一致的情况,就是由于多个事务访问相同的数据,需要实现多个事务在访问相同数据的时候进行限制,比方说上图中事务2想访问A账户的值需要等待事务提交事务之后,这样可以让并发事务的执行如同串行执行的效果一样。

二丶并发事务执行的问题:脏写,脏读,不可重复读,幻读

1.脏写

一个事务修改了另外一个未提交事务修改过的数据

  • 脏写导致一致性无法保证

    上图事务A和事务B都更新紫色数据,其中事务A首先更新为A,然后事务B过来更新为B,这时候事务A回滚后更新为Null,事务 B 明明正常写了一行数据,但是写完之后发现值变了,有点丢失更新的意思。(比如A表示余额,这时候在将余额A判断是否足以支付,判断得到可以,事务B执行扣费写入A-5,商家收到5元,结果这时候回滚了,A变成Null,事务A中转钱的一方钱变为A,钱的总额变为A+5了)

  • 脏写导致原子性受到破坏

    假如上述的事务B还操作了另外的数据,比如插入一条数据C,并且更改为B写入C是在一个事务下面的,需要具备原子性,但是脏写让B的更改需要部分回滚为Null,这样插入C和更改B就不具备原子性(比如A表示余额,这时候在将余额A判断是否足以支付,判断得到可以,事务B执行扣费写入A-5,商家收到5元,结果这时候回滚了,A变成Null,这时候部分回滚,商家的5元没用回滚,商家的库存也没用回滚,原子性被破坏)

2.脏读

如果一个事务读取到另外一个事务未提交的数据,意味着发生了脏读

比如事务A先写数据A,然后事务B督导数据A后在内存中使用A进行一系列操作(比如A表示余额,这时候在将余额A判断是否足以支付,判断得到可以)但是事务A这时候回滚了,事务B再次读取数据发现为null,这就是脏读。

脏读可能引发一致性的问题:比如事务操作时修改x和y的值,并且二者总是相等的,A修改x为1,还没来得及修改y也没用提交事务,这时候事务B读取x=1,y=0,二者不等,事务B读取到了数据库不一致的状态,读取到未提交事务的值

3.不可重复读

假如一个事务修改了另外一个事务未提交的数据,意味发生了不可重复读

比如事务A第一次读取到值为A,接着事务B修改为B,并且提交了事务B,然后事务A再次读取得到的数据是B,同一行数据多次读取值并不相同,这称作不可重复读。它是指在同一个事务里面查询同一行数据,每次查到的数据都不一样。和脏读区别在于脏读是由于别的事务回滚导致,而不可重复读读到的其实是已经提交的数据。

事务A读到事务B提交后的数据似乎很合理,但是我们想象这样一种场景:你有一个流水表和用户余额,其中记录用户每天的流水,你在月初0点的时候核对流水和库存,但是流水很多,你的程序选择一个一个用户的进行核对,核对用户甲,甲没做任何消费,但是当你核对B的时候,你将B的流水load到内存中,但是B这时候(0点30分,这一笔数据新的一个余额)进行了扣除余额的操作,导致B余额和流水对不上了。

4.幻读

如果一个事务A先根据没用搜索条件查询到一些记录,在该事务未提交前,另外一个事务写入(delete,update,insert)了符合搜索条件的记录,这时候事务A再次读取,发现数据条数和第一次读取的不同,如同出现了幻觉,称之为幻读

事务A读到事务B提交后的数据似乎很合理,但是我们想象这样一种场景:你有一个需求将会公司的男性员工了女性员工查询进行展示,你先查询了总数为100人,然后查询男性的总数50人,后查询女性人数准备在页面展示共100人,其中男50人,女50人,结果这是管理信息的人发现有一位员工性别错误录入了,将其从男修改为女,这时候你读取事务就是女51人了,你在主页显示了共100人,其中男50人,女51人

三丶隔离级别

1.Read UnCommitted 读未提交

在此隔离级别下,会发生脏读,不可重复读,和幻读

2.Read Committed 读已提交

在此隔离级别下,会发生不可重复读,和幻读

3.Repeatable Read 可重复读

在此隔离级别下,可能发生幻读

4.Serializable 可串行化

在此隔离级别下,不会发生脏读,不可重复读,和幻读

其中脏写是对一致性影响最严重的,无论是何种隔离级别,都不允许脏写发生,innodb使用锁保证不会出现脏写现象,第一个事务更新某条记录的时候,会给这条记录加锁,另外一个事务在此更新的时候,需要等待第一个事务提交释放锁后更新。隔离级别越高,其并发能力越低。

四丶Mysql设置隔离级别

默认隔离级别可重复读

1.设置全局隔离级别

SET GLOBAL TRANSACTION ISOLATION LEVEL 期望的隔离级别(可选READ UNCOMMITED,READ COMMITED,REPEATABLE READ,SERIALIZABLE),此命令只对执行语句后新产生的会话有效,对当前已经存在的会话无效

2.设置会话隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL 期望的隔离级别(可选READ UNCOMMITED,READ COMMITED,REPEATABLE READ,SERIALIZABLE),对当前会话后续事务有效,该语句可以在已开启的事务中执行,但是不会影响当前正在执行的事务,如果在事务之间执行,只会对后续的事务有效

3.设置下一个事务的隔离级别

SET TRANSACTION ISOLATION LEVEL 期望的隔离级别(可选READ UNCOMMITED,READ COMMITED,REPEATABLE READ,SERIALIZABLE) 只对当前会话的下一个即将开启的事务有效,下一个事务执行完后,后续事务将恢复到之前的隔离级别,该语句不能再已经开启的事务中执行,否则会报错。

4.指定服务器的隔离级别

在启动的时候使用--transaction-isolation=xxx即可执行默认隔离级别

五丶MVCC原理

下面讨论记录对当前事务是否可见都是基于当前事务中执行的查询是快照读(普通查询),对于当前读(select xxx for update,select xxx lock in share mode)是不通用的

1.版本链

对于InnoDB存储引擎来说,其聚簇索引记录中包含两个隐藏列:

  • trx_id:一个事务每次对聚簇索引记录做出改动的时候,都会把该事务的事务id复制给此列
  • roll_point:每次对某条聚簇索引记录进行改动的时,都会把旧的版本写入到undo 日志中,此列相当于一个指针,指向修改前的信息

每次修改都会形成Undo 日志,所有版本的数据会通过roll_point串联成一个链表,称之为版本链,头节点是当前记录的最新值。利用版本链控制多个并发事务访问相同记录时的行为称为MVCC多版本并发控制。

其实在undo日志中,只记录被更新列的信息,而不是记录全部的信息,对于没有记录的列,会通过版本链找少一个版本中的对应列的信息,直到找到聚簇索引叶子节点中的内容

2.Read View

对于使用Read Uncommitted隔离级别的事务,可以读取到没提交的数据,那么直接读取最新的版本即可。对于Serializable隔离级别,innodb直接通过加锁来访问记录。对于read committed 和 repeatable read隔离级别的事务,都必须保证督导的数据是已经提交事务修改过的记录,那么如何判断版本链中的哪个版本的数据是当前事务可见的昵?

innodb 使用的Read View

2.1 read view 的结构

  • m_ids:在生成read view时,当前系统中活跃的读写事务id列表
  • min_trx_id:生成read view时,当前系统中活跃的读写事务中最西澳的事务id,也就是m_ids中的最小值
  • max_trx_id:生成read view时,系统应该分配给下一个事务的事务id值
  • creator_trx_id:生成该read view的事务的事务id

2.2 read view 判断某个版本当前事务释放可见的步骤

  1. 如果被访问版本的trx_idcreator_trx_id相同,意味着当前事务在访问自己修改的记录,自然可见
  2. 如果访问版本的trx_id属性值小于read view中的min_trx_id 表明此版本是生成read view之前已经提交的事务,那么自然可见
  3. 如果访问版本的trx_id,大于等于read view中的max_trx_id说明,当前版本数据是生成read view后开启事务产生的,那么自然不可见
  4. 如果访问版本的trx_id 介于min_trx_idmax_trx_id之间,需要判断trx_id是否位于m_ids列表中,如果在说明创建read view时生成该版本的事务还是活跃的,那么该版本,不可被访问,如果不在说明创建read view 时生成该版本的事务已经提交,可以被访问到

如果某个版本数据对当前事务不可见那么需要一直顺着版本链找上一个版本的数据,并通过上述步骤判断是否可见,直到找到可见的版本,如果一直找不到说明该条记录对当前事务不可见,查询结果将不包含该记录。

2.3 Read Committed和 Repeatable Read的不同

  • Read Committed——每次读取数据前都生成一个Read View

    这样可以保证生成Read view 中的m_ids是实时活跃事务id集合,也许第一次读取的时候事务A没提交,其id位于m_ids中,但是第二次读取的时候事务A提交了,事务A将不位于m_ids中,这样在第二次读取的时候,通过m_ids判断事务A是否提交的时候,可以得到事务A已经提交了,然后让事务A版本产生的数据可见(见2.2.4中的内容)。

  • Repeatable Read——如果使用begin开启事务那么在第一次查询的时候生成Read view,如果使用start transaction with consistent snapshot 那么执行的时候就会生成read view

    这样可以保证当前事务从头到尾都是read view中记录的内容是一致的,第一次读取的时候事务A没有提交,那么不可见,但是第二次读取的时候事务A提交了,但是read view的m_idsmax_trx_id可以判断事务A不可见,比如事务A事务id小于max_trx_id意味着生成read view是事务A启动但是没提交,即使第二次读事务A提交了,但是m_ids中还是包含事务A,那么不可见。如果事务A事务id大于max_trx_id,那么自然第二次还是大于max_trx_id,也是不可见的,从而实现了可重复读。

2.4 二级索引与MVCC

上面我们提到,innodb聚簇索引组织的记录才具备trx_idroll_point,那么我们使用二级索引进行查询的时候,如何判断数据是否可见昵?

  1. 二级索引页面的page header中存在page_max_trx_id属性,每当有事务对其中的记录进行增删改查操作的时候,如果事务的事务id,大于page_max_trx_id,那么会更新page_max_trx_id属性值为其事务id,这意味着page_max_trx_id记录了修改该二级索引页面最大的事务id是多少。当select通过二级索引首先看下对于read view的min_trx_id是否大于该页面的page_max_trx_id,如果大于那么页面中所有记录都对该read view可见,否则就进行下面的第二步
  2. 利用二级索引中的主键值,进行回标,得到对应的聚簇索引记录然后进行回表,然后通过2.2中步骤拿到第一个可见版本的数据,然后比对此纪录和通过二级索引查询得到记录的值是否相同,如果相同那么发送给客户端,否则跳过该记录。

Mysql InnoDB多版本并发控制MVCC的更多相关文章

  1. MySQL多版本并发控制——MVCC机制分析

    MVCC,即多版本并发控制(Multi-Version Concurrency Control)指的是,通过版本链维护一个数据的多个版本,使得读写操作没有冲突,可保证不同事务读写.写读操作并发执行,提 ...

  2. MySQL学习----多版本并发mvcc

    MySQL中的大多数事务性存储引擎实现的都不是简单的行级锁.基于提升并发性能的考虑,他们一般实现了多版本并发控制(mvcc).不仅是mysql,包括oracle,postgresql等其他数据库也实现 ...

  3. 数据库原理-事务隔离与多版本并发控制(MVCC)

    刚来美团实习,正好是星期天,不得不说,其内部的资料很丰富,看了部分文档后,对数据库事务这块更理解了.数据库事务的ACID,大家都知道,为了维护这些性质,主要是隔离性和一致性,一般使用加锁这种方式.同时 ...

  4. 多版本并发控制 MVCC

    介绍多版本并发控制 多版本并发控制技术(Multiversion Concurrency Control,MVCC) 技术是为了解决问题而生的,通过 MVCC 我们可以解决以下几个问题: 读写之间阻塞 ...

  5. 转: 多版本并发控制(MVCC)在分布式系统中的应用 (from coolshell)

    from:  http://coolshell.cn/articles/6790.html 问题 最近项目中遇到了一个分布式系统的并发控制问题.该问题可以抽象为:某分布式系统由一个数据中心D和若干业务 ...

  6. MySQL多版本并发控制(MVCC)

    MVCC是行级锁的一个变种,但是它在很多的情况下避免了加锁操作,因此开销更低.MySQL,包括Oracle.PostgreSQL都实现了MVCC,虽然每个关系数据库实现不一样,但大都是实现了非阻塞的读 ...

  7. mysql-innoDB-多版本并发控制(MVCC)

    InnoDB的MVCC,是通过在每行记录后面保存三个隐藏的列来实现的其中的两个列一个保存了行的创建时间,一个保存行的过期时间(或删除时间).当然存储的并不是实际的时间值,而是系统版本号(system ...

  8. 14.2.2 InnoDB Multi-Versioning InnoDB 多版本

    14.2.2 InnoDB Multi-Versioning InnoDB 多版本: InnoDB 是一个多版本的存储引擎: 它保留信息关于改变数据的老版本,为了支持事务功能 比如并发和回滚. 这些信 ...

  9. 14.3 InnoDB Multi-Versioning InnoDB 多版本

    14.3 InnoDB Multi-Versioning InnoDB 多版本 InnoDB 是一个多版本的存储引擎,它保持信息关于改变的数据老版本的信息, 为了支持事务功能比如并发和回滚. 这些信息 ...

随机推荐

  1. SpringMVC 05: SpringMVC中携带数据的页面跳转

    SpringMVC默认的参数对象 SpringMVC默认的参数对象是指,不用再另行创建,相当于SpringMVC内置对象,可以直接声明并使用 默认的参数对象有:HttpServletRequest,H ...

  2. 【Vue学习笔记】—— vuex的语法 { }

    学习笔记 作者:o_Ming vuex Vuex ++ state ++ (用于存储全局数据) 组件访问 state 中的全局数据的方式1: this.$store.state.全局数据 组件访问 s ...

  3. Redis变慢?深入浅出Redis性能诊断系列文章(一)

    (本文首发于"数据库架构师"公号,订阅"数据库架构师"公号,一起学习数据库技术)   Redis 作为一款业内使用率最高的内存数据库,其拥有非常高的性能,单节点 ...

  4. 实用脚本:检查高 CPU / 内存消耗进程

    1 检查高 CPU 消耗进程在 Linux 上运行了多长时间的 Bash 脚本 该脚本将帮助你确定高 CPU 消耗进程在 Linux 上运行了多长时间. # vi /opt/scripts/long- ...

  5. 《吐血整理》高级系列教程-吃透Fiddler抓包教程(25)-Fiddler如何优雅地在正式和测试环境之间来回切换-下篇

    1.简介 在开发或者测试的过程中,由于项目环境比较多,往往需要来来回回地反复切换,那么如何优雅地切换呢?宏哥今天介绍几种方法供小伙伴或者童鞋们进行参考. 2.实际工作场景 2.1问题场景 (1)已发布 ...

  6. 关于成本标签管理-基于-Resource Groups & Tag Editor-统计指定Project-所有资源

    背景:因我们所有AWS都是使用Project标签作为成本标签的,今天因一个项目决定彻底退役下线 于是决定要完全清理此项目的所有资源,防止继续产生费用~ 首先想到的去通过Project 在ec2 , s ...

  7. TensorFlow搭建模型方式总结

    引言 TensorFlow提供了多种API,使得入门者和专家可以根据自己的需求选择不同的API搭建模型. 基于Keras Sequential API搭建模型 Sequential适用于线性堆叠的方式 ...

  8. composer 报错 The "https://mirrors.aliyun.com/composer/p....json" file could not be downloaded (HTTP/1.1 404 Not Found)

    [Composer\Downloader\TransportException] The "https://mirrors.aliyun.com/composer/p/provider-20 ...

  9. Vue学习之--------深入理解Vuex之多组件共享数据(2022/9/4)

    在上篇文章的基础上:Vue学习之--------深入理解Vuex之getters.mapState.mapGetters 1.在state中新增用户数组 2.新增Person.vue组件 提示:这里使 ...

  10. iframe的简单使用

    看人家写的真的是摸不着头脑.自己写.还是清楚 局部数据的刷新:可以使用ajax.这里只是简单的演示 只作:例子使用.简单演示页面跳转 a标签中target属性和iframe中的name对应.相当于将该 ...