当前位置: 首页 > news >正文

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 的解决方案。

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

相关文章:

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

文章目录 MySQL45讲 第二十讲 幻读是什么,幻读有什么问题?一、幻读的定义二、幻读带来的问题(一)语义问题(二)数据一致性问题 三、InnoDB 解决幻读的方法四、总结 MySQL45讲 第二十讲 幻读是什么&#xff0…...

MySQL技巧之跨服务器数据查询:进阶篇-从A数据库复制到B数据库的表中

MySQL技巧之跨服务器数据查询:进阶篇-从A数据库复制到B数据库的表中 基础篇已经描述:借用微软的SQL Server ODBC 即可实现MySQL跨服务器间的数据查询。 而且还介绍了如何获得一个在MS SQL Server 可以连接指定实例的MySQL数据库的连接名: MY_ODBC_MYSQ…...

【论文阅读】利用SEM二维图像表征黏土矿物三维结构

导言 在油气储层研究中,黏土矿物对流体流动的影响需要在微观尺度上理解,但传统的二维SEM图像难以完整地表征三维孔隙结构。常规的三维成像技术如FIB-SEM(聚焦离子束扫描电子显微镜)虽然可以获取高精度的3D图像,但成本…...

可靠UDP协议(KCP)使用说明

希望这篇文章&#xff0c;对学习和使用 KCP 协议的读者&#xff0c;有帮助。 1. KCPUDP 流程图 2. 示例代码&#xff08;待补充&#xff09; #include <iostream>int main() {// TODO: kcp examplereturn 0; }...

ffmpeg+D3D实现的MFC音视频播放器,支持录像、截图、音视频播放、码流信息显示等功能

一、简介 本播放器是在vs2019下开发&#xff0c;通过ffmpeg实现拉流解码功能&#xff0c;通过D3D实现视频的渲染功能。截图功能采用libjpeg实现&#xff0c;可以截取jpg图片&#xff0c;图片的默认保存路径是在C:\MYRecPath中。录像功能采用封装好的类Mp4Record实现&#xff0c…...

【Flink】-- flink新版本发布:v2.0-preview1

目录 1、简介 2、非兼容变更 2.1、API 2.2、连接器适配计划 2.3、配置 2.4、其它 3、重要新特性 3.1、存算分离状态管理 3.2、物化表 3.3、批作业的自适应执行 3.4、流式湖仓 4、附加 4.1、非兼容性的 api 程序变更 4.1.2、Removed Classes # 4.1.3、Modified Cl…...

Node.js 版本管理的最终答案 Volta

文章目录 特点安装Unix系统安装Windows系统安装 常用命令volta fetchvolta installvolta uninstallvolta pinvolta listvolta completionsvolta whichvolta setupvolta runvolta help 建议 目前对于前端项目的node 版本&#xff0c;我们一般会在项目 package.json 的 engines 字…...

蓝桥杯每日真题 - 第11天

题目&#xff1a;&#xff08;合并数列&#xff09; 题目描述&#xff08;14届 C&C B组D题&#xff09; 解题思路&#xff1a; 题意理解&#xff1a;给定两个数组&#xff0c;目标是通过若干次合并操作使两个数组相同。每次合并操作可以将数组中相邻的两个数相加&#xff…...

Vue vs React:两大前端框架的区别解析

在现代前端开发中&#xff0c;Vue.js 和 React.js 是两个最受欢迎的框架和库。我们常常面临选择它们的困惑。虽然这两者在本质上都是为了构建用户界面而设计的&#xff0c;但它们在设计理念、使用方式和生态系统等方面有着显著的区别。今天&#xff0c;我们将通过深入分析这两个…...

【树莓派raspberrypi烧录Ubuntu远程桌面登入树莓派】

提示&#xff1a;本文利用的是Ubuntu主机和树莓派4B开发板&#xff0c;示例仅供参考 文章目录 一、树莓派系统安装下载前准备工作下载安装树莓派的官方烧录软件imagerimager的使用方法 二、主机与树莓SSH连接查看数梅派IP地址建立ssh连接更新树莓派源地址 三、主机端远程桌面配…...

c# 调用c++ 的dll 出现找不到函数入口点

今天在调用一个设备的dll文件时遇到了一点波折&#xff0c;因为多c 不熟悉&#xff0c;调用过程张出现了找不到函数入口点&#xff0c;一般我们使用c# 调用c 文件&#xff0c;还是比较简单。 [DllImport("AtnDll2.dll",CharSet CharSet.Ansi)]public static extern …...

LInux——环境基础开发工具使用(正在更新中...)

1.软件包管理器 Linux下安装软件的方案&#xff1a; 1. 源代码安装 2. rpm包安装 3. 包管理器安装 --- yum/ apt &#xff08;此图片来自于比特就业课课件&#xff09; 1.1 操作生态系统 好的操作系统定义&#xff1a; 生态环境好 不同的操作系统根本是生态不同&#xff08;…...

linux 内核asmlinkage关键字总结

1&#xff0c;看一下asmlinkage的定义 CPP_ASMLINKAGE __attribute__((regparm(0))) GCC中使用__attribute__((regparm(n)))指定最多可以使用n个寄存器&#xff08;eax, edx, ecx&#xff09;传递参数&#xff0c;n的范围是0~3&#xff0c;超过n时则将参数压入栈中&#xff08;…...

⚡️如何在 React 和 Next.js 项目里优雅的使用 Zustand

前言 你是否曾感觉在 React 中管理状态简直是一场噩梦&#xff1f;如果你已经厌倦了不停地处理 props、context 和 hooks&#xff0c;那么现在是时候认识 Zustand 了。Zustand 是一个轻量级的状态管理库&#xff0c;它简化了你处理应用状态的方式。在这篇文章中&#xff0c;我…...

Pinpoint(APM)进阶--Pinot指标采集(System Metric/Inspector)

接上文 Pinpoint使用Pinot进行指标数据存储&#xff0c;Pinot流摄入需要Kafka 本文详解Kafka和Pinot的安装部署&#xff0c;以及Pinpoint的指标采集 Pinot 简介 Apache Pinot是一个实时分布式OLAP数据存储&#xff0c;专为低延迟、高吞吐量分析而构建&#xff0c;非常适合面…...

Mysql:使用binlog的一些常用技巧

1、如何查看binlog的存放路径 show variables like log% 执行结果&#xff1a; 2、如何清除binlog &#xff08;1&#xff09;按时间清除 purge binary logs before ‘2023-06-5 10:12:00’ &#xff08;2&#xff09;按文件文件名清除 purge binary logs to ‘mybinlog.0000…...

Electron 项目启动外部可执行文件的几种方式

Electron 项目启动外部可执行文件的几种方式 序言 在开发 Electron 应用程序时&#xff0c;有时需要启动外部的可执行文件&#xff08;如 .exe 文件&#xff09;。这可能是为了调用系统工具、运行第三方软件或者集成现有的应用程序。 Electron 提供了多种方式来启动外部可执行…...

前端开发中常用的包管理器(npm、yarn、pnpm、bower、parcel)

文章目录 1. npm (Node Package Manager)2. Yarn (Yarn Package Manager)3. pnpm4. Bower5. Parcel总结 前端开发中常用的包管理器主要有以下几个&#xff1a; 1. npm (Node Package Manager) 简介&#xff1a; npm 是 Node.js 的默认包管理器&#xff0c;也是最广泛使用的包…...

Linux入门:环境变量与进程地址空间

一. 环境变量 1. 概念 1️⃣基本概念&#xff1a; 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数 如&#xff1a;我们在编写C/C代码的时候&#xff0c;在链接的时候&#xff0c;从来不知道我们的所链接的动态静态库在哪里&#x…...

【Jenkins实战】Windows安装服务启动失败

写此篇短文&#xff0c;望告诫后人。 如果你之前装过Jenkins&#xff0c;出于换域账号/本地帐号的原因想重新安装&#xff0c;你大概率会遇上一次Jenkins服务启动失败提示&#xff1a; Jenkins failed to start - Verify that you have sufficient privileges to start system…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化

缓存架构 代码结构 代码详情 功能点&#xff1a; 多级缓存&#xff0c;先查本地缓存&#xff0c;再查Redis&#xff0c;最后才查数据库热点数据重建逻辑使用分布式锁&#xff0c;二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

jmeter聚合报告中参数详解

sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample&#xff08;样本数&#xff09; 表示测试中发送的请求数量&#xff0c;即测试执行了多少次请求。 单位&#xff0c;以个或者次数表示。 示例&#xff1a;…...

nnUNet V2修改网络——暴力替换网络为UNet++

更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...

算法打卡第18天

从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7…...