一、前言

建表语句和初始化语句如下:

CREATE TABLE `t`(`id` int(11) NOT NULL,`c`  int(11) DEFAULT NULL,`d`  int(11) DEFAULT NULL,PRIMARY KEY(`id`),KEY `c`(`c`)
)ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);

这个表除了主键id外,还有1个索引C,初始化语句在表中插入6行数据。

下面的语句是怎么加锁的?加的锁又是什么时候释放的?

begin;
select * from t where d = 5 for update;
commit;

比较好理解的是,这个语句会命中d=5这一行,对应的主键id=5,因此在select语句执行之后,id=5这一行记录会加一个写锁,而且由于两阶段锁协议,这个写锁,会在执行commit语句的时候释放。

由于字段d上没有索引,因此这条查询会做全表扫描,那么其他被扫描到的,但是不满足条件的5行记录上,会不会加锁呢?

众所周知,InnoDB默认的隔离级别是可重复读,所以本文在没有特殊说明的前提下,都是设定在可重复读隔离级别下的。

二、幻读是什么?

如果只在id=5,这一行加锁,而其他行不加锁的话,会怎么样呢?首先看一下这个场景。

可以看到session A里面执行了3次查询。分别是Q1、Q2和Q3。它们的sql相同,都是select * from t where d=5 for update;这条语句的意思是查所有d=5的行,而且使用的是当前读。并且加上写锁。接下来看下这三条SQL语句,会返回什么结果?

1、Q1只返回id=5的这一行;

2、在T2时刻,sessionB把id=0这一行的d值改成了5,因此T3时刻Q2查出来的是id=0和id=5这两行。

3、 在T4时刻,sessionC又插入了一行(1,1,5)。因此T5时刻Q3查出来的是id=0、id=1和id=5这三行。

其中,在T5时刻Q3读到id=1这一行的现象,被称为“幻读”。也就是说幻读指的是一个事务在前后两次查询同一范围的时候,后一次查询看到了前一次查询没有看到的值。

这里,需要对幻读做一个说明:

1、mysql在可重复读级别下,普通的查询(select * from t where id=5)是快照读,是不会看到别的事务插入的数据的的, 因此“幻读”在“当前读”下才会出现。

2、上面sessionB的修改结果,被sessionA之后的select语句用“当前读”看到,不能成为幻读。幻读仅专指“新插入的行”

三、幻读有什么问题?

1、首先是语义上的。sessionA在T1时刻就声明了,“我要把所有id=5的行锁住,不准别的事务进行读写操作”。而实际上,这个语义被破坏了。

2、其次是数据一致性的问题。这个数据不一致到底是怎么引入的?

四、如何解决幻读?

产生幻读的原因是,行锁只能锁住行,但是新插入记录的这个动作,要更新的是记录之间的“间隙”。因此,为了解决幻读问题,InnoDB只好引入新的锁,也就是间隙锁(Gap Lock)。

顾名思义,锁的就是两个值之间的空隙。如开头时的表t,初始化插入了6条记录。这就产生了7个间隙。

当你执行select * from t where d=5 for update的时候,就不止是给数据库中已有的6个记录加上行锁,还同时加上了7个间隙锁。这样就确保无法再插入新记录。 也就是说在一行行的扫描过程中,不仅给行加上了行锁,还给行2边的空隙加上了间隙锁。

间隙锁和行锁合称next-key lock,每个next-key lock都是前开后闭区间。也就是说,我们的表t初始化之后,如果用select * from t for update  要把整个表所有记录锁起来,就形成了7个next-key lock。分别是(-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20,25]、(25,supremum]。我们把间隙锁标记为开区间,把next-key lock标记为闭区间。

五、间隙锁导致死锁?

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

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

3、session B试图插入一行(9,9,9),被session A的间隙锁挡住,只好进入等待状态。

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

此时,两个session进入了相互等待状态,形成了死锁。当然InnoDB的死锁检测机制马上就发现了这对死锁,让sessionA的insert语句报错返回啦。间隙锁的引入,可能导致同样的语句,锁住了更大的范围。这其实是影响了并发度的。

如果将隔离级别设置为读提交,就没有间隙锁了。一些公司使用读提交+ binlog_format=row的组合。这样配置是否合理呢?配置是否合理,跟业务场景有关,需要具体问题具体分析。比如说,如果大家都用读提交,可是逻辑备份的时候,mysqldump要把备份线程设置为可重复读。然后,在备份期间,备份线程用的是可重复读,而业务线程用的是读提交。同时存在两种事务隔离级别,会不会有问题呢?

六、注释

1、两阶段锁协议:

先举个例子,在下面的操作序列中,事务B的update语句执行时会是什么现象呢?假设字段id是表t的主键。根间隙锁存在冲突关系的,是“往这个间隙中插入1个记录”这个操作,间隙锁之间都不存在冲突关系。

这个问题的结论取决于事务A在执行完两条update语句后,持有哪些锁,以及在什么时候释放。可以验证一下,实际上事务B的update语句会被阻塞,知道事务A执行Commit之后,事务B才能执行。事务A持有的两条记录的行锁,都是在commit的时候才释放的,也就是说,在InnoDB事务中,行锁在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放,这个就是两阶段锁协议

2、当前读和快照度:

① 快照读(snapshot read):简单的select语句(不包括select ... lock in share mode(共享锁),select ... for update(排它锁))

② 当前读(current read) :

  select ... lock in share modeselect ... for updateinsertupdate delete 

七、面试回答间隙锁的相关引申

相关知识: 间隙锁——>快照读——>行锁——>共享锁、排他锁——乐观锁、悲观锁。

解决幻读问题?

id d

1 1

5 5

10 10

select * from T where 1<=id<=5

对于当前读而言, 1、lock in share mode 2、for update

1、假如只有行锁,锁住的是id=1和id=的这两条。间隙锁的存在导致不能插入 id值为2,3,4的记录。

2、从乐观锁、悲观锁的角度而言,lock in share mode for update 都是悲观锁。乐观锁、悲观锁仅仅是一个概念。刚刚说的是mysql层面的乐观锁、悲观锁。也可以扩展到java层面的乐观锁、悲观锁。比如synchronized和CAS(compareandswap)

从另一个角度而言,lock in share mode叫作共享锁,也叫做读锁。for update 叫作排他锁。

【MySQL】幻读是什么?如何避免幻读?相关推荐

  1. MySQL面试三连杀:如何实现可重复读、又为什么会出现幻读、是否解决了幻读问题?...

    作者 | sanyuesan0000 来源 | https://blog.csdn.net/sanyuesan0000 事务隔离级别有四种,mysql默认使用的是可重复读,mysql是怎么实现可重复读 ...

  2. MySQL中的InnoDB是怎么解决幻读的?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | Aaron_涛 来源 | blog.csdn. ...

  3. MySQL事务的四种隔离级别,mysql中的不可重复读和幻读的区别,Repeatable read可重复读隔离级别下怎么不存在幻读问题?

    1. 事务的隔离级别 1.1 read uncommited:读未提交.一个事务读到了另一个事务未提交的脏数据,称之为脏读. 1.2 read commited:读已提交.解决了脏读问题,但当前事务两 ...

  4. mysql java 解决幻读_MySQL 是如何解决幻读的

    MySQL 是如何解决幻读的 一.什么是幻读 在一次事务里面,多次查询之后,结果集的个数不一致的情况叫做幻读. 而多出来或者少的哪一行被叫做 幻行 二.为什么要解决幻读 在高并发数据库系统中,需要保证 ...

  5. MySQL的InnoDB引擎是如何解决幻读的?

    目录 幻读原因 InnoDB 的三种行锁 InnoDB 的解决方案 总结 面试题 在 MySQL 中,默认的隔离级别是可重复读,可以解决脏读和不可重复读的问题,只要提升隔离级别到串行化即可解决幻读问题 ...

  6. mysql 解决了幻影读_MySQL到底能否解决幻读问题

    先说结论,MySQL 存储引擎 InnoDB 在可重复读(RR)隔离级别下是解决了幻读问题的. 方法:是通过next-key lock在当前读事务开启时,1.给涉及到的行加写锁(行锁)防止写操作:2. ...

  7. MySQL可重复读级别能够解决幻读吗

    引言 之前在深入了解数据库理论的时候,了解到事物的不同隔离级别可能存在的问题.为了更好的理解所以在MySQL数据库中测试复现这些问题.关于脏读和不可重复读在相应的隔离级别下都很容易的复现了.但是对于幻 ...

  8. mysql 虚读幻读区别_MySQL脏读、虚读、幻读

    事务的特性: 原子性:指处于同一个事务中的多条语句是不可分割的. 一致性:事务必须使数据库从一个一致性状态变换到另外一个一致性状态.比如转账,转账前两个账户余额之和为2k,转账之后也应该是2K. 隔离 ...

  9. MySQL脏读、不可重复读、幻读

    事务的特性: 原子性:指处于同一个事务中的多条语句是不可分割的. 一致性:事务必须使数据库从一个一致性状态变换到另外一个一致性状态.比如转账,转账前两个账户余额之和为2k,转账之后也应该是2K. 隔离 ...

  10. 什么是幻读?以及如何解决幻读问题?

    关注公众号ITwords,了解更多的java,大数据的相关知识,大家一起学习,一起进步. 为了便于说明问题,这一篇文章,我们就先使用一个小一点儿的表. CREATE TABLE `t` (`id` i ...

最新文章

  1. ubuntu18 安装python3.8.tgz
  2. golang key map 所有_Golang面试知识点总结
  3. 【SVM】A Practical Guide to Support Vector Classication
  4. 使用 RequireJS 优化 Web 应用前端
  5. Jmeter入门3 http请求—content-type与参数
  6. 【远程沟通】“云答辩”“云招聘”双管齐下,解救“最难毕业生”
  7. Typora + PicGo + Aliyun OSS + CSDN
  8. 第十三章 大型网站典型故障分析案例(待续)
  9. 酒店结婚播放PPT模板
  10. CMOS数字集成电路
  11. solidworks工程图模板为什么不能存为slddrt格式
  12. matlab单回路和串级控制回路,单回路和串级控制系统仿真研究
  13. 树莓派内网穿透方法大全
  14. Flutter —快速开发的IDE快捷方式
  15. JVM中的-Xms -Xmx -XXnewSize -XXMaxnewSize -Xmn -XXPermSize -XXMaxPermSize区别介绍
  16. 佛说,是我们自己苦了自己~
  17. 机器学习——Matplotlib入门教程
  18. Java版本企业电子招投标采购系统源码——功能模块功能描述+数字化采购管理 采购招投标
  19. 解决Error creating bean with name ‘redisConnectionFactory‘ defined in class path resource...问题
  20. PowerDesigner一键导入数据库所有表并画数据模型图

热门文章

  1. Android手机之间不消耗流量互传文件
  2. 对于网络的相关概念的理解
  3. 网页版 linux终端,网页版的Linux-大神之笔
  4. CSS实现文本溢出隐藏
  5. matlab经典实例,BP神经网络matlab实例(简单而经典)
  6. 微信X5调试,可以在谷歌浏览器调试
  7. matlab通信工具comm,MATLAB通信工具箱之comm.ErrorRate
  8. JavaScript 部分基础知识点
  9. EA出品的java射击类游戏,八款人见人爱的大型射击游戏,虽然相对经典但不过时...
  10. FAST AND HIGH-QUALITY SINGING VOICE SYNTHESIS SYSTEM BASED ON CONVOLUTIONAL NEURAL NETWORKS