使用过关系型数据库的,应该都事务的概念有所了解,知道事务有 ACID 四个基本属性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),今天我们主要来理解一下事务的隔离性。

声明:MySQL专栏学习系列,基本上是本人学习极客时间《MySQL实战45讲》专栏内容的笔记,并在专栏基础上进行知识点挖掘。侵删。
本人也不是什么 DBA,所以有些错误的地方请大家指正,相互交流,共同进步!

什么是事务?

数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。—— 维基百科

事务的概念看上去不难,但是需要注意以下几个点:

1、首先,事务就是要保证一组数据库操作,要么全部成功,要么全部失败;

2、在 MySQL 中,事务支持是在引擎层实现的;

3、并不是所有引擎都支持事务,如 MyISAM 就不支持,InnoDB 就支持;


今天,我们的主角是隔离性,隔离性是指当多个用户并发操作数据库时,数据库为每一个用户开启不同的事务,这些事务之间相互不干扰,相互隔离。

为什么需要隔离性?

如果事务之间不是互相隔离的,可能将会出现以下问题。

1、脏读

脏读(dirty read),简单来说,就是一个事务在处理过程中读取了另外一个事务未提交的数据。

这种未提交的数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。

还记得上节中我们提到的 dirty page 吗?这种临时处理的未提交的,都是「脏」的。

举例

时间点 事务A 事务B
1 开启事务A
2 开启事务B
3 查询余额为100
4 余额增加至150
5 查询余额为150

比如,你给小编赞赏 1 分钱,整个事务需要两个步骤:
①给小编账号加一分钱,这时小编看到了,觉得很欣慰;
②你的账号减一分钱;

但是,若该事务未提交成功,最终所有操作都会回滚,小编看到的一分钱也只是镜花水月。

2、不可重复读

不可重复读(non-repeatable read),是指一个事务范围内,多次查询某个数据,却得到不同的结果。

在第一个事务中的两次读取数据之间,由于第二个事务的修改,第一个事务两次读到的数据可能就是不一样的。

举例

时间点 事务A 事务B
1 开启事务A
2 开启事务B
3 查询余额为100
4 余额增加至150
5 查询余额为100
6 提交事务
7 查询余额为150

接着上一个例子,假设你真给小编打赏了一分钱,小编乐得屁颠屁颠地去准备提现,一查,发现真多了一分钱。

在这同时,在我还没有提现成功之前,小编的老婆已经提前将这一分钱支走了,小编此时再次查账,发现一分钱也没了。

脏读和不可重复读有点懵逼?

二者的区别是,脏读是某一事务读取了另外一个事务未提交的数据,不可重复读是读取了其他事务提交的数据。

其实,有些情况下,不可重复读不是问题,比如,小编提现期间,一分钱被老婆支走了,这不是问题!

而脏读,是可以通过设置隔离级别避免的。

3、幻读

幻读(phantom read),是事务非独立执行时发生的一种现象。

例如事务 T1 对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务 T2 又对这个表中插入了一行数据项为“1”的数据,并且提交给数据库。

而操作事务 T1 的用户如果再查看刚刚修改的数据,会发现数据怎么还是 1?其实这行是从事务 T2 中添加的,就好像产生幻觉一样,这就是发生了幻读。

举例

时间点 事务A 事务B
1 开启事务A
2 开启事务B
3 查询id<3的所有记录,共3条
4 插入一条记录id=2
5 提交事务
6 查询id<3的所有记录,共4条

其实上面的解释已经是一个例子了,但是还是要举个例子。

比如,小编准备提取你打赏的一分钱,提取完了,这时又有其他热心网友打赏了一分钱,小编一看,明明已经取出了,怎么又有一分钱!?

小编此时以为像做梦一样,我觉得也可以叫「梦读」,哈哈。

幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

事务的隔离级别

为了解决上面可能出现的问题,我们就需要设置隔离级别,也就是事务之间按照什么规则进行隔离,将事务隔离到什么程度。

首先,需要明白一点,隔离程度越强,事务的执行效率越低。

ANSI/ISO SQL 定义了 4 种标准隔离级别:

① Serializable(串行化):花费最高代价但最可靠的事务隔离级别。

“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

事务 100% 隔离,可避免脏读、不可重复读、幻读的发生。

② Repeatable read(可重复读,默认级别):多次读取同一范围的数据会返回第一次查询的快照,即使其他事务对该数据做了更新修改。事务在执行期间看到的数据前后必须是一致的。

但如果这个事务在读取某个范围内的记录时,其他事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行,这就是幻读。

可避免脏读、不可重复读的发生。但是可能会出现幻读。

③ Read committed (读已提交):保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。

可避免脏读的发生,但是可能会造成不可重复读。

大多数数据库的默认级别就是 Read committed,比如 Sql Server , Oracle。

④ Read uncommitted (读未提交):最低的事务隔离级别,一个事务还没提交时,它做的变更就能被别的事务看到。

任何情况都无法保证。

下图中是一个很好的例子,分别解释了四种事务隔离级别下,事务 B 能够读取到的结果。


看着还是有点懵逼?那我们再举个例子。

A,B 两个事务,分别做了一些操作,操作过程中,在不同隔离级别下查看变量的值:

|:-?:-?:-?:-?:-?:-?
|启动事务,查询变量V的值为1|启动事务|||||
||查询V的值为1|||||
||将V的值修改为2|||||
|查询V的值||2|1|1|1|
||提交事务B||||
|查询V的值||2|2|1|1|
|提交事务A||||||
|查询V的值||2|2|2|2|

隔离级别是串行化,则在事务 B 执行「将 1 改成 2」的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。

再次总结

读未提交:别人改数据的事务尚未提交,我在我的事务中也能读到。
读已提交:别人改数据的事务已经提交,我在我的事务中才能读到。
可重复读:别人改数据的事务已经提交,我在我的事务中也不去读。
串行:我的事务尚未提交,别人就别想改数据。

这 4 种隔离级别,并行性能依次降低,安全性依次提高。

总的来说,事务隔离级别越高,越能保证数据的完整性和一致性,但是付出的代价却是并发执行效率的低下。


隔离级别的实现

事务的机制是通过**视图(read-view)**来实现的并发版本控制(MVCC),不同的事务隔离级别创建读视图的时间点不同。

  • 可重复读是每个事务重建读视图,整个事务存在期间都用这个视图。
  • 读已提交是每条 SQL 创建读视图,在每个 SQL 语句开始执行的时候创建的。隔离作用域仅限该条 SQL 语句。
  • 读未提交是不创建,直接返回记录上的最新值
  • 串行化隔离级别下直接用加锁的方式来避免并行访问。

这里的视图可以理解为数据副本,每次创建视图时,将当前已持久化的数据创建副本,后续直接从副本读取,从而达到数据隔离效果。

隔离级别的实现

我们每一次的修改操作,并不是直接对行数据进行操作。

比如我们设置 id 为 3 的行的 A 属性为 10,并不是直接修改表中的数据,而是新加一行。

同时数据表其实还有一些隐藏的属性,比如每一行的事务 id,所以每一行数据可能会有多个版本,每一个修改过它的事务都会有一行,并且还会有关联的 undo 日志,表示这个操作原来的数据是什么,可以用它做回滚。

那么为什么要这么做?

因为如果我们直接把数据修改了,那么其他事务就用不了原先的值了,违反了事务的一致性。

那么一个事务读取某一行的数据到底返回什么结果呢?

取决于隔离级别,如果是 Read Committed,那么返回的是最新的事务的提交值,所以未提交的事务修改的值是不会读到的,这就是 Read Committed 实现的原理。

如果是 Read Repeatable 级别,那么只能返回发起时间比当前事务早的事务的提交值,和比当前事务晚的删除事务删除的值。这其实就是 MVCC 方式。

undo log

undo log 中存储的是老版本数据。假设修改表中 id=2 的行数据,把 Name=‘B’ 修改为 Name = ‘B2’ ,那么 undo 日志就会用来存放 Name=‘B’ 的记录,如果这个修改出现异常,可以使用 undo 日志来实现回滚操作,保证事务的一致性。

当一个旧的事务需要读取数据时,为了能读取到老版本的数据,需要顺着 undo 链找到满足其可见性的记录。当版本链很长时,通常可以认为这是个比较耗时的操作。

假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。

当前值是 4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view。

如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。

同时你会发现,即使现在有另外一个事务正在将 4 改成 5,这个事务跟 read-view A、B、C 对应的事务是不会冲突的。


另外,在回滚段中的 undo log 分为: insert undo log 和 update undo log:

  • insert undo log : 事务对 insert 新记录时产生的 undolog,只在事务回滚时需要,并且在事务提交后就可以立即丢弃。(谁会对刚插入的数据有可见性需求呢!!)
  • update undo log : 事务对记录进行 delete 和 update 操作时产生的 undo log。不仅在事务回滚时需要,一致性读也需要,所以不能随便删除,只有当数据库所使用的快照中不涉及该日志记录,对应的回滚日志才会被 purge 线程删除。

何时删除?

在不需要的时候才删除。也就是说,系统会判断,当没有事务再需要用到这些回滚日志时,回滚日志会被删除。

就是当系统里没有比这个回滚日志更早的 read-view 的时候。

长事务

直观感觉,一个事务花费很长时间不能够结束,就是一个长的事务,简称长事务(Long Transaction)。

长事务是数据库用户经常会碰到且是非常令人头疼的问题。长事务处理需要恰当进行,如处理不当可能引起数据库的崩溃,为用户带来不必要的损失。

根据上面的论述,长事务意味着系统里面会存在很老的事务视图。

由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的 undo log 都必须保留,这就会导致大量占用存储空间。

在 MySQL 5.5 及以前的版本,回滚日志是跟数据字典一起放在 ibdata 文件里的,即使长事务最终提交,回滚段被清理,文件也不会变小

除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库,这个我们会在后面讲锁的时候展开。

因此,我们要尽量避免长事务。

小结

这一节主要是事务的隔离级别,主要需要记住几个隔离级别、了解一下实现方式。

感觉东西有点乱,涉及了 MVCC 的东西,作者也没有展开,我能力有限,也就没有再深挖。后续,作者在涉及相关知识点时,我们再进行探讨。

不可重复读和幻读的区别
当然, 从总的结果来看, 似乎两者都表现为两次读取的结果不一致.

但如果你从控制的角度来看, 两者的区别就比较大
对于前者, 只需要锁住满足条件的记录
对于后者, 要锁住满足条件及其相近的记录


我这么理解是否可以?
避免不可重复读需要锁行就行
避免幻影读则需要锁表


不可重复读和幻读的区别
很多人容易搞混不可重复读和幻读,确实这两者有些相似。但不可重复读重点在于update和delete,而幻读的重点在于insert。

如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复 读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会 发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。

所以说不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题。

上文说的,是使用悲观锁机制来处理这两种问题,但是MySQL、ORACLE、PostgreSQL等成熟的数据库,出于性能考虑,都是使用了以乐观锁为理论基础的MVCC(多版本并发控制)来避免这两种问题。

悲观锁和乐观锁

悲观锁
正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处 于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机 制,也无法保证外部系统不会修改数据)。

在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据。

乐观锁
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。

而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如 果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

要说明的是,MVCC的实现没有固定的规范,每个数据库都会有不同的实现方式,这里讨论的是InnoDB的MVCC。

参考==>http://tech.meituan.com/innodb-lock.html

事物级别,不可重复读和幻读的区别相关推荐

  1. 事务相关、不可重复读与幻读的区别

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 事务内嵌套事务:   1) 都用spring事务时,取决spring采用的事务的隔离级别.     ...

  2. Spring事务配置的五种方式和spring里面事务的传播属性和事务隔离级别、不可重复读与幻读的区别

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. spring事务配置的五种方式 前段时间对Spring的事务配置做了比较深入的研究,在此之间对Spr ...

  3. 不可重复读和幻读的区别_图解脏写、脏读、不可重复读、幻读

    MySQL 是支持多事务并发执行的,否则来一个请求处理一个请求,处理一个人请求的时候,别的人都等着,这网站就别做了,用户都要砸键盘了. 这里就有一个问题了:一个事务在写数据的时候,另一个事务要读这行数 ...

  4. 数据库不可重复读和幻读的区别

    不可重复读:事务A第一次读取数据,事务B对同一个表进行了update 或者delete,事务A第二次读取数据,那么两次读取的数据是不同的,这时是不可重复读. 幻读:事务A第一次读取数据,事务B对同一个 ...

  5. 脏写、脏读、不可重复读、幻读的区别

    一般对于我们的业务系统去访问数据库而言,它往往是多个线程并发执行多个事务的,对于数据库而言,它会有多个事务同时执行,可能这多个事务还会同时更新和查询同一条数据,所以这里会有一些问题需要数据库来解决 我 ...

  6. 不可重复读和幻读的区别

    不可重复读是因为在读的时候没有禁止写操作,两次读取同一条数据的时候结果不一致,主要针对的是update或delete 幻读是因为在读取多条数据时,虽然此时读操作禁止了写操作,所以update和dele ...

  7. MySQL事务的四种隔离级别,mysql中的不可重复读和幻读的区别,Repeatable read可重复读隔离级别下怎么不存在幻读问题?

    1. 事务的隔离级别 1.1 read uncommited:读未提交.一个事务读到了另一个事务未提交的脏数据,称之为脏读. 1.2 read commited:读已提交.解决了脏读问题,但当前事务两 ...

  8. 不可重复读和幻读的区别_面试官:MySQL的可重复读级别能解决幻读吗

    Java面试笔试面经.Java技术每天学习一点 Java面试 关注不迷路 作者:宁愿. 来源:https://juejin.im/post/5c9040e95188252d92095a9e 引言 之前 ...

  9. 不可重复读和幻读的区别_论文重复率要多少算是合格的?

    论文重复率要多少算是合格的?对于论文查重的重复率要求,每个学校都是有一定差异的,部分高校要求比较简单,论文重复率只需要控制在20%-30%左右就可以了,而有些高校,对论文的重复率把控非常严格,明确规定 ...

  10. mysql可重复读和幻读的理解

    mysql可重复读和幻读的理解 可重复读和幻读的定义 最后总结 参考资料 很多教程和书籍对mysql的可重复读和幻读的解释都比较含糊,本文结合原理和其他的考证,深入分析下. 这里讨论的引擎是常用的In ...

最新文章

  1. python爬虫百度百科-python每日一题:网络爬虫百度百科
  2. 向大家推荐一个.Net游戏引擎:Artificial Engines
  3. python pandas 读取excel 去重某一列_Python中Pandas读取修改excel操作攻略(代码示例)...
  4. 解决uni-app官方弹框popup关闭不了问题;/pages/extUI/popup/popup;uni-app弹框popup打开调用事件。unin-app弹框封装;
  5. js `` 手机不支持
  6. mix2s 升级android p,小米推送小米MIX2S 安卓P稳定版更新 这两个隐藏升级你发现了吗?...
  7. 如何使用windows自带的远程协助
  8. c#读取ini配置文件、将配置数据保存至ini文件
  9. Java中继承和面向接口的编程
  10. 【mysql】Filesort on too many rows解决方法
  11. Servlet请求转发RequestDispatcher接口
  12. 自拟计算机作文100字,三年级自拟作文100字
  13. 阿里云OSS浏览图片报403错误的解决方法
  14. The 9-th BIT Campus Programming F. 狂乱(背包)
  15. 什么是马甲APP?怎么用马甲APP导流
  16. [转]Flex 处理bmp图片as
  17. 【P2P】【转载】P2P流媒体开源项目介绍
  18. Centos 7.9 xfs 文件系统测试
  19. easyconnect xp登录_登录说明
  20. springboot遇到的Invalid bound statement (not found): com.yc.service.UserService的众多坑

热门文章

  1. 数据库(表结构)设计技巧及注意事项
  2. Briss-最好用的pdf裁边工具
  3. 计算机导论论文论题,计算机导论专业论文题目 计算机导论毕业论文题目怎么定...
  4. 【ADNI】数据预处理(2)获取 subject slices
  5. Android文件系统编译出错记录
  6. Python 判断素数(质数)的方法讲解
  7. lisp 获取横断面数据_基于Visual LISP全路线横断面数据自动提取
  8. 只要7步,就能将任何魔方6面还原(留着以后教孩子玩
  9. 电脑怎么录制玩王者荣耀的过程
  10. 解决其他浏览器能上网谷歌浏览器不能上网