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 使用SQL语句修改表名的实现
Apr 07 MySQL
MySQL中你可能忽略的COLLATION实例详解
May 12 MySQL
MySQL注入基础练习
May 30 MySQL
MySql 缓存查询原理与缓存监控和索引监控介绍
Jul 02 MySQL
SQL实现LeetCode(177.第N高薪水)
Aug 04 MySQL
MySQL实例精讲单行函数以及字符数学日期流程控制
Oct 15 MySQL
MySQL创建定时任务
Jan 22 MySQL
MySQL中一条update语句是如何执行的
Mar 16 MySQL
Pycharm远程调试和MySQL数据库授权问题
Mar 18 MySQL
详细聊一聊mysql的树形结构存储以及查询
Apr 05 MySQL
MySQL数据库简介与基本操作
May 30 MySQL
MySQL中TIMESTAMP类型返回日期时间数据中带有T的解决
Dec 24 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 文件上传后缀名与文件类型对照表(几乎涵盖所有文件)
2010/05/16 PHP
php获取从html表单传递数组的方法
2015/03/20 PHP
php检查字符串中是否有外链的方法
2015/07/29 PHP
基于php判断客户端类型
2016/10/14 PHP
JSON PHP中,Json字符串反序列化成对象/数组的方法
2018/05/31 PHP
yii框架结合charjs实现统计30天数据的方法
2020/04/04 PHP
jQuery 插件 将this下的div轮番显示
2009/04/09 Javascript
利用JS自动打开页面上链接的实现代码
2011/09/25 Javascript
JavaScript实现找出字符串中第一个不重复的字符
2014/09/03 Javascript
angularjs 处理多个异步请求方法汇总
2015/01/06 Javascript
JavaScript实现控制打开文件另存为对话框的方法
2015/04/17 Javascript
快速解决jquery.touchSwipe左右滑动和垂直滚动条冲突
2016/04/15 Javascript
AngularJS基础 ng-submit 指令简单示例
2016/08/03 Javascript
微信小程序 实现拖拽事件监听实例详解
2016/11/16 Javascript
vue里面v-bind和Props 利用props绑定动态数据的方法
2018/08/27 Javascript
基于vue如何发布一个npm包的方法步骤
2019/05/15 Javascript
JavaScript的Proxy可以做哪些有意思的事儿
2019/06/15 Javascript
20个必会的JavaScript面试题(小结)
2019/07/02 Javascript
vue悬浮可拖拽悬浮按钮的实例代码
2019/08/20 Javascript
js实现漂亮的星空背景
2019/11/01 Javascript
JS脚本实现定时到网站上签到/签退功能
2020/04/22 Javascript
[01:54]TI4西雅图DOTA2选手欢迎晚宴 现场报道
2014/07/08 DOTA
pycharm 使用心得(七)一些实用功能介绍
2014/06/06 Python
Python3 操作符重载方法示例
2017/11/23 Python
Python实现求一个集合所有子集的示例
2018/05/04 Python
Python第三方Window模块文件的几种安装方法
2018/11/22 Python
利用django model save方法对未更改的字段依然进行了保存
2020/03/28 Python
基于python图书馆管理系统设计实例详解
2020/08/05 Python
世界上最大的艺术社区:SAA
2020/12/30 全球购物
汽车运用工程毕业生自荐信
2013/10/29 职场文书
领导干部群众路线剖析材料
2014/10/09 职场文书
公司年夜饭通知
2015/04/25 职场文书
优秀班干部主要事迹材料
2015/11/04 职场文书
小学一年级数学教学反思
2016/02/16 职场文书
python小程序之飘落的银杏
2021/04/17 Python
如何在pycharm中快捷安装pip命令(如pygame)
2021/05/31 Python