从本源来理解比较容易理解,如果只是描述概念和定义,容易让人云里雾里找不到方向.正好这两天在浏览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. Spring Cloud 学习 (九) Spring Security, OAuth2

    Spring Security Spring Security 是 Spring Resource 社区的一个安全组件.在安全方面,有两个主要的领域,一是"认证",即你是谁:二是& ...

  2. eclispe中打点不会提示的解决方法,以及自动补全

    Eclipse中打点无提示的解决办法 建了个JAVA工程,然后发现输入代码后,在输入.后面不会弹出来我所要的函数.  alt+/      提示No Default Proposals 自己找了半天, ...

  3. element ui中循环出来的表格勾选问题

    需求是这样的,一个房主屋里面有多个电表,每一个表是一个账户,一次只能给一个账户缴费,在点击go按钮进行缴费,这个时候判断是否跨表勾选,跨表格勾选则弹窗提示,反之符合需求,走缴费逻辑 上代码 <! ...

  4. .pfx和.Cer 证书

    通常情况下,作为文件形式存在的证书一般有三种格式: 第一种:带有私钥的证书,由Public Key Cryptography Standards #12,PKCS#12标准定义,包含了公钥和私钥的二进 ...

  5. 总括订单Blanket order

    总括订单Blanket order是客户向其供应方发出的采购订单,但其中包含一段时间内的多个交货日期,通常使用谈判时的预定价格.大多数情况下,它用于对消耗性商品有经常性需求的情况.总括订单通常用于客户 ...

  6. AcWing 326. XOR和路径

    大型补档计划 题目链接 如果整体来做,发现既有加法,也有整体异或,这样不容易搞. 考虑异或,各个位置互不干扰,按位考虑一下. 枚举每一位 \(k\) 发现如果设 \(f[u]\) 为这一位的期望结果还 ...

  7. k8s遇见的问题

    open /etc/docker/certs.d/registry.access.redhat.com/redhat-ca.crt: no such file or directory 解决方案   ...

  8. git clone GitLab 工程报错Repository not found

    有时使用git拉取gitlab上的项目时会出现如下的错误信息:Repository not found remote: Repository not found.fatal: repository ' ...

  9. STL—— 容器(vector)的各种功能方法

    1. 获取容器的元素个数 size() 使用 vectorName.size() 可以输出这个容器中类型的个数,如下代码: 1 #include <iostream> 2 #include ...

  10. Linux Vi进入编辑模式后使用方向键的时候,并不会使光标移动,而是在命令行中出现A、B、C、D四个字母

    在linux下,初始使用Vi的时候有两个典型的问题: 1.在编辑模式下使用方向键的时候,并不会使光标移动,而是在命令行中出现A.B.C.D四个字母: 2.当编辑出现错误,想要删除时,发现Backspa ...