1. 引言

首先,我们通过下面的SQL语句建立一张表,并插入5行数据:

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);

接着,我们执行下面这条语句:

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

for update 会为d=5的行加上一个锁,这个锁会在commit语句执行时被释放。

由于字段d上没有索引,所以会进行全表扫描。但是,在进行全表扫描的时候,不满足d=5的行会不会被加锁呢?

因为InnoDB的默认事务隔离级别是可重复读,所以下面的讨论都基于可重复读隔离级别。

2. 幻读是什么?

我们先来假设,仅仅只在d=5的行行加锁,而其他行不加锁的话,会怎么样?

假设有这样下面这样一个场景:

Session A的三次查询我们分别定义为Q1,Q2,Q3。因为都使用了for update,所以是当前读,且会对d=5的行进行加锁。我们来分析下三次查询的结果:

  1. Q1只会返回id=5的行。
  2. 由于在T2时刻,session B将id=0的d值修改成了5,因此Q2查询出来id=0和id=5这两行。
  3. 由于在T4时刻,session C插入一行(1,1,5),因此Q3查询出来id=0,id=1和id=5三行。

其中,Q3查询到id=1这种现象,我们称之为“幻读”。幻读是指一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行

有几个需要注意的点:

  1. 在可重复读隔离级别下,普通的查询时快照读,是不会看到其他事务插入的数据的。因此,幻读只会在“当前读”才会出现
  2. 在Q2中,session B修改了结果被session A的当前读看到,我们不称为时幻读。幻读专指“新插入的行”

但是,上面的场景其实是存在问题的。

第一个问题是语义。 session A在T1时刻就执行了for update,也就是语义上会将所有d=5的行锁住,不准别的事务进行读写操作。但是实际上语义就被破坏了。

又如下面这个场景:

理论上session A会在T1时刻将所有d=5的行锁住。但是session B和sessionC在后来分别修改和插入了d=5的行,但是这两行并没有被锁住,所以就违背了语义。

第二个问题是数据一致性。 数据一致性包括数据库内部数据状态的一致性、数据和日志在逻辑上的一致性。

我们来看下面这个场景:

这个场景在T1时刻的session A中加入了update语句,会把锁了的d=5的行的值修改成100。数据库的执行如下:

  1. 经过T1时刻,id=5这一行变成(5,5,100),当然这个结果会在T6时刻才提交
  2. 经过T2时刻,id=0这一行变成(0,5,5)
  3. 经过T4时刻,表里面多了一行(1,5,5)

我们再来看binlog里面的内容:

  1. T2时刻,session B事务提交,写入了两条语句
  2. T4时刻,session C事务提交,写入了两条语句
  3. T6时刻,session A事务提交,写入了update t set d=100 where d=5这条语句

所以在binlog中,由于T6时刻session A执行了update t set d=100 where d=5这条语句,所以三行变成了(0,5,100),(1,5,100),(5,5,100)。我们就会发现binlog和数据库的不一致性。

因此仅仅只在d=5的行行加锁,而其他行不加锁的假设是不合理的

如果是会在所有扫描过的行都加锁的话,我们再看下执行效果:

由于session A的Q1会将所有的行锁住,所以session B会被阻塞,直到session A提交事务才会继续执行,这样id=0的结果就会是(0,5,5),保证了数据库和binlog的一致性。

但是对所有的行加锁, 并不能阻止session C中的幻读因为在Q1语句进行加锁的时候,id=1这一行还不存在,所以没法进行加锁

3. 如何解决幻读?

产生幻读的原因是因为行锁只能锁住行,但是新插入记录这个动作,是在行的间隙中的。因此,为了解决幻读,InnoDB引入了间隙锁(Gap Lock)

在表t中,初始有6个记录,也就有7个间隙,如下图:

当执行for update的时候,不止对6个记录加入了行锁,还同时加了7个间隙锁,这就保证了无法插入新的记录。

我们之前学习过行锁,行锁是分为读锁和写锁的,这两种锁可能会存在冲突关系:

但是,间隙锁之间是不存在冲突关系的。例如下面这个场景:

session A和session B都执行了for update语句,都会加上间隙锁,但是它们具有共同的目标:保护这个间隙,不允许插入新值,因此它们之间是不会冲突的。

间隙锁和行锁合称next-key lock,每个next-key lock是前开后闭区间。next-key lock就是锁住满足条件的行和这些行前面的间隙。

间隙锁的引入解决了幻读问题,但是也存在一些问题。如下面这个问题:

我们来分析上面场景的执行流程:
我们假设id=9这一行起初是不存在的

  1. session A执行for update语句,由于id=9不存在,因此加上间隙锁(5,10)
  2. session B执行for update语句,同样加上间隙锁(5,10)
  3. session B试图插入一行(9,9,9),被session A的间隙锁挡住,进入等待
  4. session A试图插入一行(9,9,9),被session B的间隙锁挡住,进入等待

这样就出现了死锁的问题。只能让session A进行回滚了。

4. 总结

对于for update语句,它是一种悲观锁:

  • 如果查询的是主键\索引列,则是行锁。
  • 如果查询的是没有主键\索引的话,则是表锁。

对于for update加的是行锁还是表锁,可以看面试官问:select…for update会锁表还是锁行?

来源:自己整理的MySQL实战45讲笔记

幻读是什么,幻读有什么问题相关推荐

  1. mysql 幻读理解_Mysql 幻读 的一些个人理解

    背景 由于最近在准备换工作,所以开始补充一些基础知识,以前准备的时候总是去硬背一些知识点,这次花了不少时间去问了问为什么,年前对于幻读的内容有了点心得,为了不遗忘,也是为了只有能讲出来才算是真的理解了 ...

  2. 数据库基础知识ACID,隔离级别RC,RR,RU,SERIALIZABLE,Phantom Rows幻读,解决幻读,脏读dirty read

    ACID A atomicity, C consistency,I isolation, and D durability的缩写,这些特性和事务是紧密联系的,InnoDB事务的特点和ACID原则紧密联 ...

  3. mysql 什么是幻读_何为幻读?MySQL又是如何解决幻读的?

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

  4. mysql 面试知识点笔记(七)RR如何避免幻读及非阻塞读、范式

    2019独角兽企业重金招聘Python工程师标准>>> 表象:快照读(非阻塞读)--伪MVCC (Multi-Version Concurrent Controll多版本并发控制) ...

  5. 事务隔离级别——未提交读、已提交读、可重复读、串行

    事务隔离级别--未提交读.已提交读.可重复读.串行 事务隔离级别是指多个事务之间,不同事务中涉及的读写操作互相影响的隔离.其中多个事务中同时对同一条数据或者表进行写操作(insert.update.d ...

  6. MySQL怎么运行的系列(十一)快照读、锁定读、半一致性读 和 加锁语句分析

    本系列文章目录 展开/收起 MySQL怎么运行的系列(一)mysql体系结构和存储引擎 MySQL怎么运行的系列(二)Innodb缓冲池 buffer pool 和 改良版LRU算法 Mysql怎么运 ...

  7. java锁(公平锁和非公平锁、可重入锁(又名递归锁)、自旋锁、独占锁(写)/共享锁(读)/互斥锁、读写锁)

    前言 本文对Java的一些锁的概念和实现做个整理,涉及:公平锁和非公平锁.可重入锁(又名递归锁).自旋锁.独占锁(写)/共享锁(读)/互斥锁.读写锁 公平锁和非公平锁 概念 公平锁是指多个线程按照申请 ...

  8. CoLoRMap: Correcting Long Reads by Mapping short reads CoLoRMap:通过映射短读来纠正长读

    CoLoRMap: Correcting Long Reads by Mapping short reads CoLoRMap:通过映射短读来纠正长读 Motivation: 第二代测序技术为测序基因 ...

  9. java:1221是一个非常特殊的数,它从左边读和从右边读是一样的,编程求所有这样的四位十进制数。

    资源限制 时间限制:1.0s 内存限制:512.0MB 问题描述 1221是一个非常特殊的数,它从左边读和从右边读是一样的,编程求所有这样的四位十进制数. 输出格式 按从小到大的顺序输出满足条件的四位 ...

  10. java imap 标记已读,JavaMail通过IMAP和POP3接收未读以及设置已读邮件

    JavaMail通过IMAP和POP3接收未读以及设置已读邮件 博客分类: javamail javamailpop3imap 使用javaMail收邮件主要有两种协议,一种是pop3,一种是imap ...

最新文章

  1. 《C# WinForM 实践开发教程》案例×××(2)
  2. php网页 安装插件,插件安装流程
  3. 正则表达式与相关工具
  4. UE4 调试着色器编译过程
  5. pg库和mysql的优缺点_MySQL与PostgreSQL的实际性能比较
  6. HCIE Security IPSec 备考笔记(幕布)
  7. iptv网关服务器系统 自己刷,iptv网关服务器镜像系统
  8. java实现url编码与中文的互相转换
  9. 遍历目录下的所有文件(文件)
  10. 超级无敌屌炸天位运算快读
  11. 软件质量保证与测试大作业,软件测试大作业..docx
  12. 手机怎么查看pe服务器信息,宏视监控手机版服务器
  13. 第七届科技节获奖及建模论文相似度名单公示
  14. UVA - 1600 Patrol Robot (巡逻机器人)(bfs)
  15. java实现一元多项式减法_一元多项式 加法 减法 乘法
  16. CentOS 与 Ubuntu:哪个更适合做服务器
  17. camera杂项---两种shutter
  18. Google Android 开发者网站更新了
  19. Unity 支持 3ds max 2021 物理材质吗?(FBX 出口)是否应该使用BPR材质?
  20. 解决vue-router报NavigationDuplicated: Avoided redundant navigation to current location: “/login“ 的问题

热门文章

  1. 微信请求错误:未能解析此远程名称
  2. 同花顺选股python开发_量化之路-python绘图-高仿同花顺绘制股票K线图+均线+成交量+MACD+KDJ(附代码)...
  3. enumerate函数详解
  4. 当前视频号直播电商是个小风口
  5. android 版本点开黑屏,Android手机锁屏之后打开就会黑屏报错
  6. nginx 配置文件正确性测试
  7. 王者s19服务器维护到什么时候,王者荣耀维护几点结束 王者荣耀s19赛季开始时间新英雄介绍 王者荣耀3月31日更新内容汇总...
  8. 【Python】整理20个Pandas统计函数
  9. 02-Python turtle 模块精讲
  10. 【机器学习面试题】—— 卷积神经网络