从本源来理解比较容易理解,如果只是描述概念和定义,容易让人云里雾里找不到方向.正好这两天在浏览mysql的文档,我可以简单在这里总结一下,帮助其他还没有理解的朋友,如果有错误也麻烦帮忙指正.

先讲一点背景知识:

首先明确一点,数据库的命令的执行者的封装基本抽象是Transaction,语句的执行都会有对应的Transaction对象,并且都会有对应的id来标识不同的Transaction.Transaction Id按照不同的时序来分配, 通俗点说,时间上来执行的早的transaction id小一点, 晚的transaction id大一点.不是说只有我们手动去start transaction这样才会创建transaction.我们执行一个简单的autocommit类型的语句,比如insert或者update语句,都会对应生成一个新的事务.

所以,所有SQL语句执行皆有其对应的transaction id, transaction id的大小可以表示SQL语句执行发生的早晚.

Mysql提供了多版本读取能力.什么叫多版本?就是说表中一份存储的数据行会有多个版本,这个版本对应的version就是transaction id.当行数据被某个transaction变更时, 这个行会有个隐含的hidden column中记录了这个更新者的transaction id.旧的数据在被覆盖的同时,会存储到一个undolog的文件中,这个文件中就是保存着每一行的版本数据链表,沿着这个链表走我们可以走过这一行数据之前的版本变更.

(这个undolog数据量不需要那么大,比如一个行数据的更新者transaction id小于所有server中活跃的transaction ids,那么这个行数据的undolog中的数据就可以删除掉,因为不会有transaction来去查询这个行记录的undolog)

一,现在来说什么是可重复读:

当开始一个新的transaction,这个transaction会分配一个新的transactionid,此时在这个transaction内部如果我们执行一条普通的select语句,根据select语句的condition,我们会找到一些满足条件的行记录,先不着急返回:

1.如果此时是mysql默认的repeatable read隔离模式

每个匹配的行记录,我们从前面说的hidden column中找到对应的最近一次执行更新操作的操作者transaction id,我们将这个id(也就是版本),和我们当前的transaction id进行比较,如果大于执行语句所属的transaction的id,那么就需要去undolog文件中去寻找旧的版本,一直找到小于当前transaction id的版本的行记录,这个就作为快照数据返回.其他的行记录都是这样来处理的.

在repeatable read隔离模式中,所有的普通select语句(consistent read)(非select ... for update的语句)都会进行这样的比对过程来返回数据.也就是说,即使在我们当前这个事务执行过程中,有其它事务执行插入了新的数据能够满足我们的查询条件,但因为这个数据的版本(transaction id)大于我们当前事务的id,我们是查询不到这个数据的.

这也就是成为可重复查的原因,不管多少次查询,每次读取的都是同样的版本的数据,也就是字面的意思.

2.如果此时是read committed隔离模式

对于普通的查询select语句不再有上面这个版本的限制,每次查询,只需要返回满足条件的最新的行记录即可.所以此时就不是可重复读,也就是说我们可以看到别的事务提交的数据(所以名字叫read committed嘛)

二, 然后来说什么是幻读:

幻读的字面意思很简单,就是在事务内(这里强调同一个事务)两次查询语句(注意是select ...for...update语句,不是前面的普通查询语句<consistent read>),读取到了不同的数据.

这个从情理上来看是很正常的时情,首先因为数据库是一个支持大量并发任务的服务,那么我们在事务执行的过程中,新的数据插入并发生变化也是很正常的事情,所以这不是BUG.

注意,这里说到了并发, 在编程语言里面我们知道,出现了并发问题的一个最最常见的情况就是,check and do这样的操作,这样不是并发操作安全的,因为check和do的过程中可能会插入其它的操作,所以当我们进行do操作的时候,先前的条件可能已经不满足了.

那么在编程语言中我们是怎么解决问题的呢?

也就是加锁,对我们关注的数据(面向对象语言中的对象)进行加锁操作,其实也就是独占,我这个任务在拿着这个锁的时候,别的人都靠边站(都阻塞在那里),我这个任务做完了,其它任务才可以去做.

在数据库中也是同样的问题,我们的select..where..condition..for update也是先圈定一个感兴趣的数据(满足condition的行数据),然后进行update操作,在这个事务进行过程中面临的也是并发的问题.

那么参考编程语言的做法,我们的方式也是独占,独占的目标是什么呢?

只是满足条件的行数据吗?

当然不止,比如condition: a>5 and a<10,目前只有一条记录a=7,如果我们只锁这个记录可以吗?不可以,因为其它事务可能还会做新的插入操作,比如插入一条a=9的记录,假如我们的业务逻辑是,如果当前a在5-10的区间中只有一条记录,我们就可以删除这个记录(这个业务逻辑很奇怪,先假设是这样),在上一次查询的时候是只有一条行记录,我们认为满足了条件,现在我们就要删除这个记录,却发现5-10中有了2条记录,那么此时的删除操作就是一个误操作....

数据库解决这个问题的办法就是,对这个区间都加锁,不仅仅是已有的行记录,空的那些a的值[8,9]都会加上锁.也就是gap lock,这个时候我们当前的事务就达到了对这个区间独占的目的,其它的事务在我们处理过程中就无法插入新的数据了: ).

当然, 是否独占,加不加区间的锁,这些mysql给我们了自由选择的权力,在READ COMMITED下,就不会加上区间锁,只会锁住已有的记录,所以此时如果有其它事务插入新的数据,当然也可以成功.如果在REAPEATABLE-READ下,就是会执行上面说的独占操作,其它想在这个区间插入数据的事务就得等在那里了(阻塞住了).

讲到这里,我觉得幻读和可重复读的关系应该理清了,

1.repeatable read模式下,我们执行select...for..update独占了满足条件的记录,阻断了其它事务的变更,不会出现幻读的情况.

在read committed模式下,不独占,会有并发问题,会出现查到新的数据的问题.

阻断幻读的目的往往在于我们有check-and-do这样的业务逻辑需求,来实现更进一步严格的原子性需求.

2.对于可重复读,是数据库多版本的一种福利,帮助我们实现在事务中能够实现锁定时间点读取快照的目的.

最后说一个区间锁的场景,

比如我们需要业务场景中经常会有类似这样一个需求,当表中存在满足某个条件的一行数据,我们就不做.如果不存在我们就插入一条记录.此时,我们就可以利用上面说的解决幻读的办法,使用repeatable模式,因为它会锁定对应的条件,在我们select...for..update过程中,可以保证不会有其它事务插入.这样我们的表中就不会出现因为并发问题,导致无法实现唯一性的问题了.

读者点评一下上述文章的几个核心点

1.首先对于普通的增、修改、删除操作MYSQL默认开启一个事务;不要以为没有事务开启,只是完成操作就结束了

2.如果数据库开启了可重复读的隔离模式,那就需要知道以下几个事情:

1)快照读

我们执行一条普通的select查询语句由于默认会开启一个新事务,分配一个事务ID,那么mysql会去判断(undo文件)自己是不是事务ID最小的,如果不是,那么尽管有比自己大的,有可能并发下,有新的数据插入,那也不管,只返回比自己小的数据!

2)间隙锁

  对于select ...for update 如果是区间查询 比如 a>5 and a<10  mysql做法是启用间隙锁,它会选择锁住一个区间范围;此时是一个独占的概念,另外的事务插入、修改都没办法进行,需要等待这个事务被释放,起到一个串行的概念!

作者:扣鼎之歌
链接:https://www.zhihu.com/question/38507762/answer/968486962
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

如何理解SQL的可重复读和幻读之间的区别?的更多相关文章

  1. SQL Server 中的事务与事务隔离级别以及如何理解脏读, 未提交读,不可重复读和幻读产生的过程和原因

    原本打算写有关 SSIS Package 中的事务控制过程的,但是发现很多基本的概念还是需要有 SQL Server 事务和事务的隔离级别做基础铺垫.所以花了点时间,把 SQL Server 数据库中 ...

  2. SQL Server中的事务与其隔离级别之脏读, 未提交读,不可重复读和幻读

    原本打算写有关 SSIS Package 中的事务控制过程的,但是发现很多基本的概念还是需要有 SQL Server 事务和事务的隔离级别做基础铺垫.所以花了点时间,把 SQL Server 数据库中 ...

  3. [MySQL]对于事务并发处理带来的问题,脏读、不可重复读、幻读的理解

    一.缘由 众所周知MySQL从5.5.8开始,Innodb就是默认的存储引擎,Innodb最大的特点是:支持事务.支持行级锁. 既然支持事务,那么就会有处理并发事务带来的问题:更新丢失.脏读.不可重复 ...

  4. .NET:脏读、不可重复读和幻读测试

    目录 背景脏读原因重现和避免不可重复读原因重现和避免幻读原因重现和避免嵌套事务导致的死锁备注 背景返回目录 昨天才发现如果一条数据被A事务修改但是未提交,B事务如果采用“读已提交”或更严格的隔离级别读 ...

  5. MySQL事务(脏读、不可重复读、幻读)

    1. 什么是事务? 是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作:这些操作作为一个整体一起向系统提交,要么都执行.要么都不执行:事务是一组不可再分割的操作集合(工作逻辑单元): ...

  6. Spring 事务与脏读、不可重复读、幻读

    索引: 目录索引 参看代码 GitHub: 1.Spring 事务 2.事务行为 一.Spring 事务: Spring 的事务机制是用统一的机制来处理不同数据访问技术的事务处理. Spring 的事 ...

  7. Hibernate中的事务隔离问题(脏读、不可重复读、幻读)

    Hibernate中的事务隔离问题(脏读.不可重复读.幻读) 1.事务的特性 事务的四个特性: 1)原子性:事务是进行数据库操作的最小单位,所以组成事务的各种操作是不可分割的 2)一致性:组成事务的各 ...

  8. mysql系列:加深对脏读、脏写、可重复读、幻读的理解

    关于相关术语的专业解释,请自行百度了解,本文皆本人自己结合参考书和自己的理解所做的阐述,如有不严谨之处,还请多多指教. 事务有四种基本特性,叫ACID,它们分别是: Atomicity-原子性,Con ...

  9. hibernate事务并发问题(脏读,不可重复读,幻读)

    脏读  dirty read:  读了别的事务没有提交的事务, 可能回滚, 数据可能不对. 不可重复读 non repeatable read: 同一个事务里前后读出来的数据不一样, 被另一个事务影响 ...

随机推荐

  1. 零钱问题的动态规划解法——用 n 种不同币值的硬币凑出 m 元,最少需要多少硬币。

    输入格式:第一行输入需要凑的钱数 m 和硬币的种类 n (0<m<100,0<n<10),第二行输入 n 种硬币的具体币值,假设硬币供应量无限多. 输出格式:输出最少需要的硬币 ...

  2. PyQt(Python+Qt)学习随笔:QTreeWidget的topLevelItemCount属性

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 QTreeWidget的topLevelItemCount属性是一个只读属性,用于保存树型部件中顶层 ...

  3. PyQt(Python+Qt)学习随笔:QTreeView树形视图的uniformRowHeights属性

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 uniformRowHeights属性用于控制视图中所有数据项是否保持相同高度,所有高度都与视图中第 ...

  4. 第15.3节 PyCharm程序调试功能介绍

    一. 代码调试 点击工具栏的调试按钮(如下图蓝色圈标记按钮)可以进行程序调试,可以在调试前先设置断点,断点设置就是在打开文件的行与前面的行号之间用鼠标单击进行设置和取消(如下图蓝色下划线上面的实体圆点 ...

  5. 添加和读取Resources嵌入资源文件(例如.dll和.ssk文件)

    前言:有些程序运行的时候,可能调用外部的dll,用户使用时可能会不小心丢失这些dll,导致程序无法正常运行,因此可以考虑将这些dll嵌入到资源中,启动时自动释放.对于托管的dll,我们可以用打包软件合 ...

  6. javascript中 fn() 和 return fn() 的区别

    在js中用return和不用return,输出结果有的时候傻傻搞不清,之前在网上看到个例子挺经典,不过讲的不清楚,上例子: var i = 0; function fn(){    i++;   if ...

  7. 介质访问控制子层-Medium Access Control Sublayer:多路访问协议、以太网、无线局域网

    第四章 介质访问控制子层-Medium Access Control Sub-layer 4.1介质访问控制子层概述 MAC子层不属于之前提到的OSI或TCP/IP架构的任何一层,这也是为什么这一层被 ...

  8. sql server如何把退款总金额拆分到尽量少的多个订单中

    一.问题 原来有三个充值订单,现在要退款450元,如何分配才能让本次退款涉及的充值订单数量最少?具体数据参考下图: 二.解决方案 Step 1:对可退金额进行降序排列,以便优先使用可退金额比较大的订单 ...

  9. AWT01-体系概述

    1.概述 AWT(Abstract Window Toolkit),中文译为抽象窗口工具包,该包提供了一套与本地图形界面进行交互的接口,是Java提供的用来建立和设置Java的图形用户界面的基本工具. ...

  10. Greenplum 性能优化之路 --(一)分区表

    一.什么是分区表 分区表就是将一个大表在物理上分割成若干小表,并且整个过程对用户是透明的,也就是用户的所有操作仍然是作用在大表上,不需要关心数据实际上落在哪张小表里面.Greenplum 中分区表的原 ...