一:准备

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

    • 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. Android中相对布局的两个控件

    <Button android:id="@+id/button3" android:layout_width="wrap_content" android ...

  2. 大数据学习之路之HBASE

    Hadoop之HBASE 一.HBASE简介 HBase是一个开源的.分布式的,多版本的,面向列的,半结构化的NoSql数据库,提供高性能的随机读写结构化数据的能力.它可以直接使用本地文件系统,也可以 ...

  3. 利用pgAgent创建定时任务

    使用Postgresql自带的pgAgent,可以很方便地创建定时执行任务.现在网上的资料都比较旧,Postgresql版本更新得比较快,导致网上的一些教程都不合用了,现在我写分享一下自己的经验. P ...

  4. sql server for centos7

    sql server for centos7 笔者在CENTOS7上面安装SQL SERVER,感觉非常方便. 但有一点要注意,字段是字符串类型的,要使用nvarchar(),不能使用varchar( ...

  5. 【转】禁用chrome firefox 的 WebRTC功能防止真实IP泄漏

    无论是使用VPN还是其它代理方式,很多时候我们不希望暴露自己的真实IP,且一直以来我们认为VPN是安全的,所有流量都会走VPN. 但最近暴露出一个WebRTC特性,会暴露我们的真实IP.适用浏览器:c ...

  6. Seurat V3.0

    最新版V3文档:https://satijalab.org/seurat/vignettes.html 不要再用V2的版本了,V3已经涵盖了V2所有的功能. 最新版3.0已经发布了,有重大更新,以前的 ...

  7. java查询数据库数据时报错antlr/ANTLRException

    在集成SH项目时,写hql 语句总是查不出东西,而且报 java.lang.NoClassDefFoundError: antlr/ANTLRException,郁闷了很久在网上终于找到了答案,原来是 ...

  8. springlcoud中使用consul作为注册中心

    好久没写博客了,从今天开始重新杨帆起航............................................ springlcoud中使用consul作为注册中心. 我们先对比下注册 ...

  9. nginx关闭日志

    # access_log off; access_log /dev/null; error_log /dev/null;

  10. jvm 命令使用调优 通过jstat、jmap对java程序进行性能调优

    转载:http://blog.csdn.net/jerry024/article/details/8507589 转载: https://blog.csdn.net/zhaozheng7758/art ...