MySQL45讲 第二十讲 幻读是什么,幻读有什么问题?

文章目录

  • MySQL45讲 第二十讲 幻读是什么,幻读有什么问题?
    • 一、幻读的定义
    • 二、幻读带来的问题
      • (一)语义问题
      • (二)数据一致性问题
    • 三、InnoDB 解决幻读的方法
    • 四、总结

MySQL45讲 第二十讲 幻读是什么,幻读有什么问题?

在数据库事务处理的复杂世界里,幻读是一个不容忽视的重要概念。它不仅关乎数据的一致性,还与事务隔离性紧密相连。今天,我们就一同深入探讨幻读的奥秘,解析其定义、所引发的问题,以及 InnoDB 是如何巧妙解决这一难题的。


一、幻读的定义

幻读究竟是什么呢?简单来说,在可重复读隔离级别下,当一个事务对同一个范围进行前后两次查询时,后一次查询竟然发现了前一次查询中未曾出现的行。这就好比在一个神秘的魔法世界里,数据会 “凭空” 出现或消失,让人捉摸不透。

假设有一个名为t的表,其结构如下:

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外,还有一个索引c,并且已经初始化插入了 6 行数据。

现在,我们考虑这样一个事务操作序列。在事务 A 中,执行了三次查询语句select * from t where d = 5 for update,分别标记为 Q1、Q2 和 Q3。根据事务可见性规则,这些查询使用了当前读,应该能够读到所有已提交记录的最新值。

在这里插入图片描述

  • Q1 查询时,表中只有id = 5这一行满足d = 5的条件,所以 Q1 只返回了这一行。
  • 在 T2 时刻,事务 B 执行了update t set d = 5 where id = 0,将id = 0这一行的d值修改为 5。此时,事务 A 的 Q2 查询就会看到id = 0id = 5这两行,因为事务 B 的修改已经提交,Q2 需要读到最新值。
  • 接着,在 T4 时刻,事务 C 插入了一行(1,1,5)。当事务 A 执行 Q3 查询时,就会看到id = 0id = 1id = 5这三行。

这里,Q3 读到id = 1这一行的现象就是幻读。需要注意的是,在可重复读隔离级别下,普通查询是快照读,不会看到其他事务插入的数据,幻读仅在当前读下才会出现。而且,幻读特指新插入的行,像事务 B 的修改结果被事务 A 之后的查询用当前读看到,并不属于幻读范畴。


二、幻读带来的问题

(一)语义问题

从语义角度来看,幻读会导致事务的加锁声明失去意义。

就像事务 A 在 T1 时刻声明要锁住所有d = 5的行,禁止其他事务进行读写操作。然而,由于幻读的存在,事务 B 可以修改id = 0这一行的d值为 5,事务 C 还能插入新的(1,1,5)行,这显然破坏了事务 A 的加锁语义。

我们通过一个详细的场景来进一步说明。假设事务 B 和事务 C 在执行修改和插入操作时,还分别执行了其他相关操作:

session Asession Bsession C
T1begin; select * from t where d = 5 for update; / * Q1 * /
T2update t set d = 5 where id = 0; update t set c = 5 where id = 0;

T3select * from t where d = 5 for update; / * Q2 * /
T4insert into t values(1,1,5); update t set c = 5 where id = 1;
T5select * from t where d = 5 for update; / * Q3 * /
T6commit;

事务 B 的第二条语句update t set c = 5 where id = 0,语义是修改id = 0d = 5这一行的c值为 5。但由于事务 A 在 T1 时刻只给id = 5这一行加了行锁,没有锁住id = 0这行,所以事务 B 在 T2 时刻可以执行这两条更新语句,这就与事务 A 中 Q1 语句要锁住所有d = 5的行的语义相违背。同样,事务 C 对id = 1这一行的修改也破坏了 Q1 的加锁声明。

(二)数据一致性问题

幻读还会引发数据一致性问题,这涉及到数据库内部数据状态以及数据和日志在逻辑上的一致性。

我们在事务 A 的 T1 时刻添加一个更新语句update t set d = 100 where d = 5,然后分析整个执行序列完成后的情况。

经过 T1 时刻,id = 5这一行变成(5,5,100),最终在 T6 时刻提交。T2 时刻,id = 0这一行变为(0,5,5);T4 时刻,表中新增了一行(1,5,5)。此时,数据库中的数据看起来似乎没有问题。

但是,当我们查看 binlog 中的内容时,就会发现问题。T2 时刻,事务 B 提交,写入了两条语句;T4 时刻,事务 C 提交,写入了两条语句;T6 时刻,事务 A 提交,写入了update t set d = 100 where d = 5这条语句。按照 binlog 的执行顺序,最终id = 0id = 1这两行的结果会变成(0,5,100)(1,5,100),与数据库中的实际结果不一致。

这种数据不一致的情况非常严重,它可能导致数据的错乱,影响系统的正常运行。例如,在一个电商系统中,如果出现这种数据不一致,可能会导致订单信息错误、库存数量不准确等问题,给企业带来巨大的损失。


三、InnoDB 解决幻读的方法

为了解决幻读问题,InnoDB 引入了一种新的锁机制 —— 间隙锁(Gap Lock)。间隙锁,顾名思义,就是锁住两个值之间的空隙。

以我们之前的表t为例,初始化插入 6 个记录后,会产生 7 个间隙,分别是(-∞,0)(0,5)(5,10)(10,15)(15,20)(20,25)(25,+∞)。当执行select * from t where d = 5 for update时,InnoDB 不仅会给已有的 6 个记录加上行锁,还会同时给这 7 个间隙加上间隙锁,确保无法再插入新的记录。

间隙锁和行锁合称 Next - Key Lock,每个 Next - Key Lock 是前开后闭区间。例如,当使用select * from t for update要锁住整个表所有记录时,就会形成 7 个 Next - Key Lock,分别是(-∞,0](0,5](5,10](10,15](15,20](20, 25](25, +supremum]。这里的+supremum是 InnoDB 为每个索引添加的一个不存在的最大值,用于满足前开后闭区间的定义。

间隙锁的引入虽然解决了幻读问题,但也带来了一些新的困扰。由于间隙锁会锁定更大的范围,可能会导致并发度下降,甚至引发死锁。

例如,考虑这样一个业务逻辑:任意锁住一行,如果该行不存在则插入,如果存在则更新其数据。

在这里插入图片描述

在并发情况下,可能会出现死锁现象。假设两个事务 A 和 B 都试图执行这个逻辑,且都要操作id = 9这一行(假设该行初始不存在)

  • 事务 A 先执行select * from t where id = 9 for update,由于id = 9不存在,会加上间隙锁(5,10)
  • 接着事务 B 也执行select * from t where id = 9 for update,同样加上间隙锁(5,10),此时间隙锁之间不冲突,事务 B 的语句可以执行成功。
  • 然后事务 B 试图插入一行(9,9,9),但被事务 A 的间隙锁挡住,进入等待状态。
  • 而事务 A 此时也试图插入(9,9,9),同样被事务 B 的间隙锁挡住,两个事务就进入了互相等待的死锁状态。

当然,InnoDB 的死锁检测机制会及时发现这种死锁关系,并让其中一个事务的插入语句报错返回,以避免系统长时间阻塞。

如果想要避免间隙锁带来的这些问题,还有一种配置选择,就是将隔离级别设置为读提交。在这种隔离级别下,就没有间隙锁了,但需要将 binlog 格式设置为 row,以解决可能出现的数据和日志不一致问题。不过,这种配置是否合理,需要根据具体的业务场景来分析。如果业务不需要可重复读的保证,读提交隔离级别下操作数据的锁范围更小,可能是一个合理的选择。但如果盲目跟风使用这种配置,而没有考虑业务实际需求,可能会在后续的运行中出现各种问题。


四、总结

  • 幻读在数据库事务处理中是一个复杂而关键的问题,它对数据的一致性和事务的隔离性有着重要影响。通过本文的详细分析,我们了解到幻读的定义、产生的问题以及 InnoDB 的解决方案。

  • 在实际应用中,我们需要深入理解这些概念,根据业务需求合理选择事务隔离级别和配置。如果对间隙锁等机制理解不足,可能会导致生产库上出现死锁等问题,影响系统的性能和稳定性。希望本文能够帮助读者更好地掌握幻读相关知识,在数据库设计和开发中做出明智的决策,构建出更加可靠、高效的数据库应用系统。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/470658.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FatLab:我的编程课程系列

FatLab 是一款教程类软件。 大概是因为我的编程生涯始于自学,FatLab便也保持了这种气息:从一个“自然生长”的角度提供了一套C语言教程。 教程方面,目前仅完成了《C语言基础要素》系列。正如其名,这个系列仅探讨了语言中非常基础…

冗余连接2 hard题 代随C#写法

此题在卡码网109与力扣685题亦有记载 有一说一C#写法我没咋搞懂 就看明白了思路 这里贴一个答案待后续我醒悟了再来看罢 难就难在对整体数据结构classUnion(并查集)的理解不熟并且 对于输入输出这个迭代过程理解上也比较吃力 109. 冗余连接II 题…

【QT】QSS

个人主页~ 一、QSS QSS可以说是拿了CSS的一部分过来用,是CSS的简化版本 1、基本语法 选择器 {属性名:属性值; }将界面上所有的QPushButton文本颜色都改为红色 QPushButton {color:red; }2、设置方式 (1)指定控件样式设置 在widget.cpp中…

java模拟键盘实现selenium上下左右键 table中的左右滚动条实现滚动

在这篇文章中,我们将学习如何使用Java编程语言模拟键盘输入,特别是模拟上下左右方向键的操作。这是一个很有趣的项目,尤其适合刚入行的开发者。我们将分步进行,接下来,我们会通过表格展示整个实现过程,然后…

JQuery封装的ajax

1. 注意&#xff1a; 首先要导jq的包json对象可以用 . 来调用keyjava只能给前端传页面&#xff0c;或者打印的内容String jsonstr json.toJSONString(resultJSON); //将对象转为JSON对象 Json格式和参数解释&#xff1a; <script src"js/jquery-1.10.2.min.js&quo…

文献解读-DNAscope: High accuracy small variant calling using machine learning

关键词&#xff1a;基准与方法研究&#xff1b;基因测序&#xff1b;变异检测&#xff1b; 文献简介 标题&#xff08;英文&#xff09;&#xff1a;DNAscope: High accuracy small variant calling using machine learning标题&#xff08;中文&#xff09;&#xff1a;DNAsc…

vue中如何关闭eslint检测?

ESLint作为一个用于JavaScript代码的验证工具&#xff0c;主要用于检查代码语法和编码规范。本文旨在指导那些希望在Vue.js项目中禁用ESLint验证功能的用户。对于需要这一操作的朋友&#xff0c;以下内容将提供参考。 vue中如何关闭eslint检测&#xff1f; 有了eslint的校验&…

用vscode编写verilog时,如何有信号定义提示、信号定义跳转(go to definition)、模块跳转这些功能

&#xff08;一&#xff09;安装插件SystemVerilog - Language Support 安装一个vscode插件即可&#xff0c;插件叫SystemVerilog - Language Support。虽然说另一个插件“Verilog-HDL/SystemVerilog/Bluespec SystemVerilog”也有信号提示及定义跳转功能&#xff0c;但它只能提…

️️一篇快速上手 AJAX 异步前后端交互

AJAX 1. AJAX1.1 AJAX 简介1.2 AJAX 优缺点1.3 AJAX 前后端准备1.4 AJAX 请求基本操作1.5 AJAX 发送 POST 请求1.6 设置请求头1.7 响应 JSON 数据1.8 AJAX 请求超时与网络异常处理1.9 取消请求1.10 Fetch 发送 Ajax 请求 2. jQuery-Ajax2.1 jQuery 发送 Ajax 请求&#xff08;G…

❤React-React 组件通讯

❤ React 组件通讯 组件通讯将教我们的内容&#xff1a; 能够使用道具接收数据W能够实现父子组件之间的通讯能够实现兄弟组件之间的通讯能够给组件添加道具校验能够说出生命周期常用的钩子函数能够知道高阶组件的作用 1、 组件通讯介绍 组件是独立且封闭的单元&#xff0c;…

【初阶数据结构与算法】链表刷题之移除链表元素、反转链表、找中间节点、合并有序链表、链表的回文结构

文章目录 一、移除链表元素思路一思路二 二、合并两个有序链表思路&#xff1a;优化&#xff1a; 三、反转链表思路一思路二 四、链表的中间节点思路一思路二 五、综合应用之链表的回文结构思路一&#xff1a;思路二&#xff1a; 一、移除链表元素 题目链接&#xff1a;https:…

POI实现根据PPTX模板渲染PPT

目录 1、前言 2、了解pptx文件结构 3、POI组件 3.1、引入依赖 3.2、常见的类 3.3、实现原理 3.4、关键代码片段 3.4.1、获取ppt实例 3.4.2、获取每页幻灯片 3.4.3、循环遍历幻灯片处理 3.4.3.1、文本 3.4.3.2、饼图 3.4.3.3、柱状图 3.4.3.4、表格 3.4.3.5、本地…

计算机毕业设计Python+Neo4j知识图谱医疗问答系统 大模型 机器学习 深度学习 人工智能 大数据毕业设计 Python爬虫 Python毕业设计

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

【机器学习】机器学习中用到的高等数学知识-2.概率论与统计 (Probability and Statistics)

概率分布&#xff1a;理解数据的分布特征&#xff08;如正态分布、伯努利分布、均匀分布等&#xff09;。期望和方差&#xff1a;描述随机变量的中心位置和离散程度。贝叶斯定理&#xff1a;用于推断和分类中的后验概率计算。假设检验&#xff1a;评估模型的性能和数据显著性。…

Scala入门基础(17.1)Set集习题

一.选择题 二.实训 图书馆书籍管理系统相关的练习。内容要求&#xff1a; 1.创建一个可变 Set&#xff0c;用于存储图书馆中的书籍信息 &#xff08;假设书籍信息用字符串表示&#xff0c;如“Java编程思想”“Scala实战”等&#xff09; 2.添加两本新的书籍到图书馆集合中&a…

移动端【01】面试系统的MVVM重构实践

基于MVVM的移动端面试系统重构实践&#xff1a;模块化设计与实现 一、项目背景 面试记录表系统在经过一年多的迭代后&#xff0c;代码质量问题日益突出。View和ViewModel代码均超过3000行&#xff0c;组件引用超过1000个&#xff0c;亟需进行架构重构。本文将详细介绍基于MVV…

Springboot 启动端口占用如何解决

Springboot 启动端口占用如何解决 1、报错信息如下 *************************** APPLICATION FAILED TO START ***************************Description:Web server failed to start. Port 9010 was already in use.Action:Identify and stop the process thats listening o…

基于Python+Django+Vue3+MySQL实现的前后端分类的商场车辆管理系统

项目名称&#xff1a;基于PythonDjangoVue3MySQL实现的前后端分离商场车辆管理系统 技术栈 开发工具&#xff1a;PyCharm、Visual Studio Code (VSCode)运行环境&#xff1a;Python 3.10、MySQL 8.0、Node.js 18技术框架&#xff1a;Django 5、Vue 3.4、Ant-Design-Vue 4.12 …

ML 系列: 第 23 节 — 离散概率分布 (多项式分布)

目录 一、说明 二、多项式分布公式 2.1 多项式分布的解释 2.2 示例 2.3 特殊情况&#xff1a;二项分布 2.4 期望值 &#xff08;Mean&#xff09; 2.5 方差 三、总结 3.1 python示例 一、说明 伯努利分布对这样一种情况进行建模&#xff1a;随机变量可以采用两个可能的值&#…

Openstack7--安装消息队列服务RabbitMQ

只需要在控制节点安装 安装RabbitMQ yum -y install rabbitmq-server 启动RabbitMQ并设置开机自启 systemctl start rabbitmq-server;systemctl enable rabbitmq-server 创建 rabbitmq 用户 并设置密码为 000000 rabbitmqctl add_user rabbitmq 000000 如果你不慎创错了…