一:准备

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

    • 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. Sublime Text3注册及汉化(支持Windows、MAC OS)

    苹果mac 版本下载 点击下载https://download.sublimetext.com/Sublime%20Text%20Build%203114.dmg PART_A 注册 v3103及以上 ...

  2. 团队作业-Alpha(1/4)

    队名:软工9组 组长博客: https://www.cnblogs.com/cmlei/ 作业博客: 组员进度 ● 组员一(组长) 陈明磊 ○过去两天完成了哪些任务 ●文字/口头描述 初步学习flas ...

  3. Open vSwitch系列实验(二):Open vSwitch的GRE隧道实验网络

    一.实验目的 了解GRE协议及原理 理解 Open vSwitch如何配置GRE隧道 二.实验原理 Open vSwitch创建GRE原理很简单,就是把对GRE头和外部IP头的一些操作从原来的代码中抽 ...

  4. Oracle 获取表的主键、外键以及唯一约束条件

    Oracle 获取表的主键.外键以及唯一约束条件 Select a.Owner 主键拥有者, a.table_name 主键表, b.Column_Name 主键列, b.Constraint_Nam ...

  5. Linux 操作系统 & High Tech

    分享10大白帽黑客专用的 Linux 操作系统 - 51CTO.COMhttp://os.51cto.com/art/201905/597156.htm Ubuntu 创始人谈论为什么 Linux 在 ...

  6. Jenkins自动化版本构建

    1.拉取代码 2.更新父版本 更新依赖版本 3.打包并推送到maven私库 4.版本控制后提交代码并打成docker镜像 PS:修改pom.xml项目版本,这里我没使用插件,直接使用脚本进行修改,这样 ...

  7. 廖雪峰Git教程2

    转自:https://www.liaoxuefeng.com/wiki/896043488029600 [远程仓库] 到目前为止,我们已经掌握了如何在Git仓库里对一个文件进行时光穿梭,你再也不用担心 ...

  8. SynchronizedStack -- tomcat8同步栈

    同步栈(安全栈): org.apache.tomcat.util.collections.SynchronizedStack通过stack栈锁来控制栈中获取的类T.通过push.pop和clear方法 ...

  9. Docs-.NET-C#-指南-语言参考-关键字-值类型:内置数值转换

    ylbtech-Docs-.NET-C#-指南-语言参考-关键字-值类型:内置数值转换 1.返回顶部 1. 内置数值转换(C# 参考) 2019/10/22 C# 提供了一组整型和浮点数值类型. 任何 ...

  10. realsense SDK编译 release

    1>------ 已启动生成: 项目: ZERO_CHECK, 配置: Release x64 ------1> Checking Build System1> CMake does ...