一:准备

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

    • 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. C格式字符串转为二叉树

    最近在LeetCode做题,二叉树出现错误时不好排查,于是自己写了一个函数,将前序遍历格式字串转换成二叉树. 形如 "AB#D##C##" 的字符串,"#"表示 ...

  2. Ubuntu -- unknown error: Chrome failed to start: exited abnormally (Driver info: chromedriver=...)

    #添加沙盒模式 chrome_options.add_argument("--no-sandbox")

  3. 【Tomcat】本地域名访问配置

    原路径:localhost:8080/jsja 1.把8080端口改为80端口 打开%TOMCAT_HOME%/conf/server.xml <Connector connectionTime ...

  4. Zygote启动及其作用

    目录 1.Zygote简介 2.Zygote进程如何启动 2.1 init.zygote64_32.rc文件 2.2 查看ps信息 2.3 启动 3.Zygote作用 3.1 启动system_ser ...

  5. [转]Git 代码撤销、回滚到任意版本(当误提代码到本地或master分支时)

    两种情况(场景) 情况一      代码还只在本地,未push到运程仓库,想把代码还原到上一次commit的代码,此时操作为代码撤销 解决方案: 1 git reset [--hard|soft|mi ...

  6. JS如何判断文字是全角还是半角

    载自:http://www.php.cn/js-tutorial-362638.html 全角:是一种电脑字符,是指一个全角字符占用两个标准字符(或两个半角字符)的位置.全角占两个字节.半角:是指一个 ...

  7. 使用hwclock读取rtc中的时间时报错"hwclock: ioctl(RTC_RD_TIME) to /dev/rtc0 to read the time failed: No such device or address"如何处理?

    1. No such device or address 这一句表明当前的板子上没有这样的外设,检查设备树和硬件连接情况 2. 笔者是这样解决的 由于设备树中为rtc所指定的总线与硬件上的连接rtc的 ...

  8. percona mysql5.7进程出现大量unauthenticated user解决记录

    现象:http://task.chinasoft.com.wx/ 所有任务能打开,我的任务打开很慢 有些人能用,有些人不能用,数据库出现大量的未认证用户连接,连接数利用率超过70% 重启nginx,a ...

  9. jenkins结合supervisor进行python程序发布后的自动重启

    jenkins结合supervisor进行python程序发布后的自动重启 项目背景: 通过jenkins发布kvaccount.chinasoft.com站点的python服务端程序,业务部门同事需 ...

  10. IDEA 修改某个Module名称

    一.选择module右键——>Refactor——>Rename 二.修改该module下的pom.xml文件对应module名改掉 三.修改项目的pom文件中modules里的modul ...