一:准备

  - 为了深入了解幻读,准备数据。

    • CREATE TABLE `t` (
      `id` int() NOT NULL,
      `c` int() DEFAULT NULL,
      `d` int() DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `c` (`c`)
      ) ENGINE=InnoDB;
      insert into t values(,,),(,,),(,,),(,,),(,,),(,,);

  

  - 思考

    - 下面的语句是什么时候加锁,什么时候释放锁的呢?

    • begin;
      select * from t where c= for update;
      commit;

    - InnoDB 的默认事务隔离级别是可重复读,所以下面的问题,都是设定在可重复读隔离级别下。

    - 其他隔离级别会在末尾讲述。

二:什么是幻读?

  - 幻读(读已提交隔离级别下的表现)

    - 

  

  - 问题解读

    - 可以看到,session A 里执行了三次查询,分别是 Q1、Q2 和 Q3。它们的 SQL 语句相同,都是 select * from t where d=5 for update。

    - 这个语句的意思你应该很清楚了,查所有 d=5 的行,而且使用的是当前读,并且加上写锁。

    - 红字中是每个事务读到的结果,会发现,每次返回的记录都不同。

  

  - 幻读定义

     - 也就是说,幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。

三:幻读存在的问题?

  - 语意问题

    - session A 在 T1 时刻就声明了,“我要把所有 d=5 的行锁住,不准别的事务进行读写操作”。

    - 而实际上,这个语义被破坏了。

  - 数据一致性问题

    - 这个一致性,不止是数据库内部数据状态在此刻的一致性,还包含了数据和日志在逻辑上的一致性。

  - 那我把经过的记录都加上锁,能解决这个问题了么?

    - 

    - 可以看出,即使对所有经过的记录加锁,也不能阻止 session A 读到多余的记录。

    - 原因很简单。在 T3 时刻,我们给所有行加锁的时候,id=1 这一行还不存在,不存在也就加不上锁。

    - 也就是说,即使把所有的记录都加上锁,还是阻止不了新插入的记录。

    - 所以,InnoDB为了解决幻读,设计出了一种新锁。

四:如何解决幻读?(也是可重复读隔离级别的实现原理)

  - 使用间隙锁

    - 现在你知道了,产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。

    - 因此,为了解决幻读问题,InnoDB 只好引入新的锁,也就是间隙锁 (Gap Lock)。

    - 顾名思义,间隙锁,锁的就是两个值之间的空隙。

    - 比如文章开头的表 t,初始化插入了 6 个记录,这就产生了 7 个间隙。

    - 

    - 这样,当你执行 select * from t where d=5 for update 的时候,就不止是给数据库中已有的 6 个记录加上了行锁,还同时加了 7 个间隙锁。

      - 这样就确保了无法再插入新的记录。

    - 也就是说这时候,在一行行扫描的过程中,不仅将给行加上了行锁,还给行两边的空隙,也加上了间隙锁。

    - 现在你知道了,数据行是可以加上锁的实体,数据行之间的间隙,也是可以加上锁的实体。

    - 但是间隙锁跟我们之前碰到过的锁都不太一样。

  - 间隙锁和行锁

    - 行锁的冲突关系为

      - 

      - 也就是说,跟行锁有冲突关系的是“另外一个行锁”。

    - 间隙锁的冲突关系为

      -  跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。

      - 通过下面的死锁来解释这个锁关系

        - 

        - 间隙锁死锁解释

          - session A 执行 select … for update 语句,由于 id=9 这一行并不存在,因此会加上间隙锁 (5,10);

          - session B 执行 select … for update 语句,同样会加上间隙锁 (5,10),间隙锁之间不会冲突,因此这个语句可以执行成功;

          - session B 试图插入一行 (9,9,9),被 session A 的间隙锁挡住了,只好进入等待;

          - session A 试图插入一行 (9,9,9),被 session B 的间隙锁挡住了。

          - 至此,两个 session 进入互相等待状态,形成死锁。

          - 当然,InnoDB 的死锁检测马上就发现了这对死锁关系,让 session A 的 insert 语句报错返回了。

  - 理解了间隙锁的冲突关系后

    - 通过间隙锁和 next-key lock 的引入,帮我们解决了幻读的问题,但是可能会导致同样的语句锁住更大的范围,这其实是影响了并发度的。

五:回头看 问题 select * from t where d=5 for update; 的锁?

  - 前提是在该字段有索引的情况下

    - 因为 InnoDB 的加锁是根据索引加锁的,在没有索引的字段上执行,会导致锁全表

  - 在读提交的隔离级别下

    - 读提交隔离级别下,在语句执行完成后,是只有行锁的。

    - 而且语句执行完成后,InnoDB 就会把不满足条件的行行锁去掉。

  - 在可重复读的隔离级别下

    - 由于间隙锁,会锁住 0-5/5-10 两个间隙范围。

《Mysql - 幻读》的更多相关文章

  1. 简单物联网:外网访问内网路由器下树莓派Flask服务器

    最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...

  2. 利用ssh反向代理以及autossh实现从外网连接内网服务器

    前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...

  3. 外网访问内网Docker容器

    外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...

  4. 外网访问内网SpringBoot

    外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...

  5. 外网访问内网Elasticsearch WEB

    外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...

  6. 怎样从外网访问内网Rails

    外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...

  7. 怎样从外网访问内网Memcached数据库

    外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...

  8. 怎样从外网访问内网CouchDB数据库

    外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...

  9. 怎样从外网访问内网DB2数据库

    外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...

  10. 怎样从外网访问内网OpenLDAP数据库

    外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...

随机推荐

  1. Hadoop NameNode 元数据以及查看元数据的方式

    HDFS中NameNode工作机制1.NameNode的主要功能(1)负责客户端请求的响应: (2)负责元数据的管理. 2.元数据管理namenode对数据管理采用了三种存储形式: (1)内存元数据: ...

  2. tomcat启动startup.bat一闪而过(分析与解答)

    tomcat启动startup.bat一闪而过(分析与解答) 方法/步骤     在正确配置Tomcat环境变量后,遇到很多次运行startup.bat后,一个窗口一闪而过的.为了分析导致tomcat ...

  3. Java 从入门到进阶之路(十六)

    在之前的文章我们介绍了一下 Java 中类的多态,本章我们来看一下 Java 中类的内部类. 在 Java 中,内部类分为成员内部类和匿名内部类. 我们先来看一下成员内部类: 1.类中套类,外面的叫外 ...

  4. 信息论 | information theory | 信息度量 | information measures | R代码(一)

    这个时代已经是多学科相互渗透的时代,纯粹的传统学科在没落,新兴的交叉学科在不断兴起. life science neurosciences statistics computer science in ...

  5. Java Hessian实践

    Hessian是基于HTTP的轻量级远程服务解决方案,Hessian向RMI一样,使用二进制进行客户端和服务端的交互.但是与其它二进制远程调用技术(例如RMI)不同的是,它的二进制消息可以移植到其它非 ...

  6. MySQL两地三中心方案初步设计【转】

    整体内容会按照如下的方式来进行设计: 首先说下方案的背景,我参考了一些资料(参见附件). 方案背景 随着互联网业务快速发展,多IDC的业务支撑能力和要求也逐步提升,行业内的“两地三中心”方案较为流行. ...

  7. python PIL/cv2/base64相互转换

    PIL和cv2是python中两个常用的图像处理库,PIL一般是anaconda自带的,cv2是opencv的python版本.base64在网络传输图片的时候经常用到. ##PIL读取.保存图片方法 ...

  8. Web 性能压力测试工具之 Siege 详解

    Siege是一款开源的压力测试工具,设计用于评估WEB应用在压力下的承受能力.可以根据配置对一个WEB站点进行多用户的并发访问,记录每个用户所有请求过程的相应时间,并在一定数量的并发访问下重复进行.s ...

  9. Flutter响应式编程 - Stream

    1.前言 在Dart库中,有两种实现异步编程的方式(Future和Stream),使用它们只需要在代码中引入dart:async即可. 本文主要介绍Stream的相关概念及利用其异步特性来实现简单的响 ...

  10. ISO/IEC 9899:2011 条款6.5.5——乘法操作符

    6.5.5 乘法操作符 语法 1.multiplicative-expression: cast-expression multiplicative-expression    *    cast-e ...