MySQL如何解决幻读问题


Posted in MySQL onAugust 07, 2021

前言

  我们知道MySQL在可重复读隔离级别下别的事物提交的内容,是看不到的。而可提交隔离级别下是可以看到别的事务提交的。而如果我们的业务场景是在事物内同样的两个查询我们需要看到的数据都是一致的,不能被别的事物影响,就使用可重复读隔离级别。这种情况下RR级别下的普通查询(快照读)依靠MVCC解决“幻读”问题,如果是“当前读”的情况需要依靠什么解决“幻读”问题呢?这就是本博文需要探讨的。

  在探讨前可以看下之前的博文(MySQL是如何实现事务隔离?),主要介绍隔离级别的具体技术细节,读过以后看此篇文章可能更有帮助。

  注:本博文讨论的“幻读”都是指在“可重复读”隔离级别下进行。

一、什么是幻读?

  假设我们有表t结构如下,里面的初始数据行为:(0,0,0),(1,1,1),(2,2,2),(3,3,3),(4,4,4),(5,5,5)

CREATE TABLE `t`
(
    `id` INT(11) NOT NULL,
    `key`  INT(11) DEFAULT NULL,
    `value`  INT(11) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `value` (`value`)
) ENGINE = InnoDB;
INSERT INTO t
VALUES (0, 0, 0),
       (1, 1, 1),
       (2, 2, 2),
       (3, 3, 3),
       (4, 4, 4),
       (5, 5, 5)

  假设select * from where value=1 for update,只在这一行加锁(注意这只是假设),其它行不加锁,那么就会出现如下场景:

MySQL如何解决幻读问题

Session A的三次查询Q1-Q3都是select * from where value=1 for update,查询的value=1的所有row。

  • T1:Q1只返回一行(1,1,1);
  • T2:session B更新id=0的value为1,此时表t中value=1的数据有两行
  • T3:Q3返回两行(0,0,1),(1,1,1)
  • T4:session C插入一行(6,6,1),此时表t中value=1的数据有三行
  • T5:Q3返回三行(0,0,1),(1,1,1),(6,6,1)
  • T6:session A事物commit。

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

先对“幻读”做出如下解释:

  • 在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此, 幻读在“当前读”下才会出现(三个查询都是for update表示当前读);
  • 上面session B的修改update结果,被session A之后的select语句用“当前读”看到,不能称为幻读,幻读仅专指“新插入的行”。

二、幻读有什么问题?

(1)需要单独解决

  众所周知,select ...for update语句就是将相应的数据行锁住,比如session A在T1时刻的Q1查询语句:select * from where value=1 for update就是将value=1的数据行锁住,但显然如果是上述的场景发生,此时的for update语义被破坏了(并没有锁住value=1的数据行)。

  即使把所有的记录都加上锁,还是阻止不了新插入的记录,所以“幻读”问题要单独拿出来解决。没法依靠MVCC或者行锁机制来解决。这就引出“间隙锁”,是另外一种加锁机制。

(2)间隙锁引发的并发度

  间隙锁引入以后,可能会导致同样语句锁住更大的范围,这可能就会影响了并发度。具体请看下面介绍

三、如何解决幻读?

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

  间隙:比如表中加入6个记录,0,5,10,15,20,25。则产生7个间隙:

MySQL如何解决幻读问题

  在一行行扫描的过程中,不仅将给行加上了行锁,还给行两边的空隙也加上了间隙锁。这样就确保了无法再插入新的记录。

  间隙锁和行锁合称next-key lock,每个next-key lock是前开后闭区间(间隙锁开区间,next-key lock前开后闭区间):

  间隙锁与间隙锁之间是不存在冲突的,冲突的是往间隙里插入一条记录。 

MySQL如何解决幻读问题

  表t中是没有value=7这个数据的,所以Q1加的间隙锁(1,5),而Q2也是加的这个间隙锁,两者不冲突都是为了保护这个间隙不允许插入值。

  在表t初始化后,假设表的数据如下:

MySQL如何解决幻读问题

  如果用select * from for update执行,则会把整个表所有记录锁起来,就形成了7个next-key lock,分别是(-∞,0]、(0,2]、(2,4]、(4,6]、(6,8]、(8, 10]、(10, +supremum]

  间隙锁的引入,可能会导致同样的语句锁住更大的范围,是会影响了并发度

  假设发生如下场景:

MySQL如何解决幻读问题

 则明显发生了死锁,分析如下:

  • Q1:执行select …for update语句,由于id=9这一行并不存在,因此会加上间隙锁 (8,10);
  • Q2:执行select …for update语句,同样会加上间隙锁(8,10),间隙锁之间不会冲突,因 此这个语句可以执行成功;
  • session B 试图插入一行(9,9,9),被session A的间隙锁挡住了,只好进入等待;
  • session A试图插入一行(9,9,9),被session B的间隙锁挡住了。

  有上述可知间隙锁的引入,可能会导致同样语句锁住更大的范围,这其实是影响了并发度。

  为了解决幻读问题可以采用读可提交隔离级别,间隙锁是在可重复读隔离级别下才会生效的。所以如果把隔离级别设置为读提交的话, 就没有间隙锁了。但同时,你要解决可能出现的数据和日志不一致问题,需要把binlog格式设置为row,也就是说采用“RC隔离级别+日志格式binlog_format=row”组合。

三、总结

  • RR隔离级别下间隙锁才有效,RC隔离级别下没有间隙锁;
  • RR隔离级别下为了解决“幻读”问题:“快照读”依靠MVCC控制,“当前读”通过间隙锁解决;
  • 间隙锁和行锁合称next-key lock,每个next-key lock是前开后闭区间;
  • 间隙锁的引入,可能会导致同样语句锁住更大的范围,影响并发度。

到此这篇关于MySQL如何解决幻读问题的文章就介绍到这了,更多相关MySQL 幻读内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

MySQL 相关文章推荐
MySQL主从复制断开的常用修复方法
Apr 07 MySQL
MySQL复制问题的三个参数分析
Apr 07 MySQL
教你解决往mysql数据库中存入汉字报错的方法
May 06 MySQL
MySQL 百万级数据的4种查询优化方式
Jun 07 MySQL
mysql联合索引的使用规则
Jun 23 MySQL
mysql连接查询中and与where的区别浅析
Jul 01 MySQL
MySQL 1130异常,无法远程登录解决方案详解
Aug 23 MySQL
MySQL学习之基础操作总结
Mar 19 MySQL
MySQL实战记录之如何快速定位慢SQL
Mar 23 MySQL
MySQL数据库查询进阶之多表查询详解
Apr 08 MySQL
深入理解mysql事务隔离级别和存储引擎
Apr 12 MySQL
MySQL索引失效场景及解决方案
Jul 23 MySQL
浅谈MySQL之select优化方案
Aug 07 #MySQL
SQL实现LeetCode(197.上升温度)
Aug 07 #MySQL
SQL实现LeetCode(196.删除重复邮箱)
Aug 07 #MySQL
MySQL Shell import_table数据导入的实现
Aug 07 #MySQL
MySQL配置主从服务器(一主多从)
SQL实现LeetCode(180.连续的数字)
Aug 04 #MySQL
Mysql中where与on的区别及何时使用详析
Aug 04 #MySQL
You might like
PHP的栏目导航程序
2006/10/09 PHP
添加到收藏夹代码(兼容几乎所有的浏览器)
2007/01/09 Javascript
自己使用js/jquery写的一个定制对话框控件
2014/05/02 Javascript
js实现带有介绍的Select列表菜单实例
2015/08/18 Javascript
js判断文本框输入的内容是否为数字
2015/12/23 Javascript
Zabbix添加Node.js监控的方法
2016/10/20 Javascript
AngularJS实现动态添加Option的方法
2017/05/17 Javascript
js防刷新的倒计时代码 js倒计时代码
2017/09/06 Javascript
微信小程序 setData 对 data数据影响问题
2019/04/18 Javascript
layui自定义ajax左侧三级菜单
2019/07/26 Javascript
浅谈laytpl 模板空值显示null的解决方法及简单的js表达式
2019/09/19 Javascript
layui表格设计以及数据初始化详解
2019/10/26 Javascript
Vue项目前后端联调(使用proxyTable实现跨域方式)
2020/07/18 Javascript
JavaScript点击按钮生成4位随机验证码
2021/01/28 Javascript
python代码检查工具pylint 让你的python更规范
2012/09/05 Python
详解JavaScript编程中的window与window.screen对象
2015/10/26 Python
详解Django之auth模块(用户认证)
2018/04/17 Python
Pyinstaller打包.py生成.exe的方法和报错总结
2019/04/02 Python
Tensorflow 1.0之后模型文件、权重数值的读取方式
2020/02/12 Python
python查看矩阵的行列号以及维数方式
2020/05/22 Python
python 获取字典特定值对应的键的实现
2020/09/29 Python
python 实现学生信息管理系统的示例
2020/11/28 Python
导出HTML5 Canvas图片并上传服务器功能
2019/08/16 HTML / CSS
美国一家专业的太阳镜网上零售商:Solstice太阳镜
2016/07/25 全球购物
猫途鹰英国网站:TripAdvisor英国(旅游社区和旅游评论)
2016/08/30 全球购物
中国跨镜手机配件批发在线商店:TVC-Mall
2019/08/20 全球购物
欧洲最古老的鞋厂:Peter Kaiser
2019/11/05 全球购物
String这个类型的class为何定义成final?
2012/11/13 面试题
万户网络JAVA程序员岗位招聘笔试试卷
2013/01/08 面试题
旅游管理本科生求职信
2013/10/14 职场文书
2014年幼儿园植树节活动方案
2014/03/02 职场文书
物理教育专业求职信
2014/06/25 职场文书
平面设计专业求职信
2014/08/09 职场文书
学前班幼儿评语大全
2014/12/29 职场文书
正规借条模板
2015/05/26 职场文书
《失物招领》教学反思
2016/02/20 职场文书