从本源来理解比较容易理解,如果只是描述概念和定义,容易让人云里雾里找不到方向.正好这两天在浏览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. Tree--二叉树BinarySearchTree

    BinarySearchTreeMap的实现 1 public interface Map<K extends Comparable<K>, V> { 2 void put(K ...

  2. 第15.21节 PyQt(Python+Qt)入门学习:QListView的作用及属性详解

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.概述 QListView是从QAbstractItemView 派生的类,实现了QAbstrac ...

  3. 深入浅出让你理解什么是LLVM

    什么是LLVM 转载自https://www.jianshu.com/p/1367dad95445 LLVM项目是模块化.可重用的编译器以及工具链技术的集合. 美国计算机协会 (ACM) 将其2012 ...

  4. 5、Spring Cloud Ribbon

    1.Ribbon简介 (1).Ribbon介绍 Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具. Ribbon是Netflix发布的开源项目,主 ...

  5. 【题解】P3631 [APIO2011]方格染色

    很有意思的一道题,所以单独拿出来了. 完整分享看 这里 题目链接 luogu 题意 有一个包含 \(n \times m\) 个方格的表格.要将其中的每个方格都染成红色或蓝色.表格中每个 \(2 \t ...

  6. 题解 CF830D Singer House

    \(\texttt{Solution}\) 首先考虑 \(\texttt{dp}\) 维护题目要求的深度为 \(i\), 每个节点最多经过一次的不同有向路径数量 \(f_i\). 明显的,只维护这个东 ...

  7. mp4文件导致迅雷监视下载问题

    原因:没有为mp4设置响应的mime Type,默认就是附件的形式. 应该为其设置mime type类型  video/mp4 <staticContent> <mimeMap fi ...

  8. Emlog整站搬家教程

    之前使用的服务器空间到期,不想再续费,所以就打算更换服务器,购买新的服务器之后不知道怎么把EM程序转移到新的服务器上面,所以东查查西查查,终于找到了一个既简单又方便的搬家方法,分享给大家.步骤开始:1 ...

  9. proxySQL with MGR

    环境信息 hostname IP port role comm ms81 192.168.188.81 3399 master ms82 192.168.188.82 3399 slave ms83 ...

  10. Day5 - 01 函数及函数的调用概念

    函数就是最基本的一种代码抽象的方式.函数只需写一次,就可以多次调用.Python本身内置了很多有用的函数,可以直接调用. 调用函数    要调用一个函数,需要知道函数的名称和参数.可以通过help(x ...