MySQL中的幻读问题
1. 什么是幻读?
幻读是一种数据库事务中可能出现的并发问题,具体表现为:在同一个事务中,前后两次查询的结果集不同,仿佛“幻影”一般,出现了原本不存在的数据。
1.1 具体表现:
-
现象描述
事务 A 在某个范围内查询了多行记录,并对这些记录进行了修改。
随后,事务 B 在这个范围内插入了一条新记录。
当事务 A 再次查询时,会发现多了一行之前不存在的记录。 -
幻读 VS 脏读/不可重复读
- 脏读:事务读取到了另一个未提交事务的修改内容。
- 不可重复读:事务前后读取同一行数据时,内容发生了变化。
- 幻读:事务范围查询时,前后读取的结果集中多了新的“行”。
1.2 案例示例:
假设有一个 students 表,存储学生成绩记录:
| id | name | score |
|---|---|---|
| 1 | Alice | 85 |
| 2 | Bob | 90 |
-
事务 A:启动事务并执行以下查询:
SELECT * FROM students WHERE score > 80;返回结果为:
id name score 1 Alice 85 2 Bob 90 -
事务 B:插入一条新记录:
INSERT INTO students (id, name, score) VALUES (3, 'Charlie', 88); -
事务 A:再次执行相同查询:
SELECT * FROM students WHERE score > 80;返回结果为:
id name score 1 Alice 85 2 Bob 90 3 Charlie 88
这时,事务 A 发现了一条“幻影”数据(Charlie),这就是幻读。
2. 快照读如何避免幻读?
2.1 什么是快照读?
快照读指的是通过 MVCC(多版本并发控制)机制读取数据的一种方式,它读取的是某个时刻的“历史快照”,而不是当前最新的实际数据。
- 快照读依赖于 InnoDB 引擎实现的 MVCC。
- 使用快照读时,即使其他事务对数据进行了插入或修改,当前事务依然能看到事务开始时的数据状态。
2.2 快照读的特点
-
读取旧版本数据:
每次查询的结果是事务开始时生成的快照,不受其他事务影响。 -
无需加锁:
快照读不需要像“当前读”那样使用锁机制,效率更高。 -
典型语句:
对于常见的SELECT语句,如果未显式使用锁机制,则是快照读。例如:SELECT * FROM students WHERE score > 80;
2.3 快照读如何避免幻读?
在 可重复读(REPEATABLE READ)隔离级别下,MySQL 使用 MVCC 为事务生成一致性视图。
- 每个事务在开始时,会记录当前的数据版本快照。
- 对于后续的
SELECT操作,返回的结果总是基于这一快照,即使其他事务对数据进行了增删改,也不会影响快照读的结果。
举例说明:
假设在事务 A 开始时,students 表的状态如下:
| id | name | score |
|---|---|---|
| 1 | Alice | 85 |
| 2 | Bob | 90 |
-
事务 A:
START TRANSACTION; SELECT * FROM students WHERE score > 80;返回结果为:
id name score 1 Alice 85 2 Bob 90 -
事务 B:插入一条新数据:
INSERT INTO students (id, name, score) VALUES (3, 'Charlie', 88); -
事务 A:再次查询时:
SELECT * FROM students WHERE score > 80;返回结果依然是事务开始时的快照:
id name score 1 Alice 85 2 Bob 90
这样,事务 A 避免了幻读,因为它读取的快照与事务开始时一致。
2.4 快照读的局限性
快照读仅适用于普通查询操作。如果事务需要对数据进行修改(如 UPDATE、DELETE、INSERT),必须使用“当前读”,而当前读中可能会涉及幻读。
3. 当前读如何避免幻读?
3.1 什么是当前读?
当前读指的是读取数据库中最新的、实时的数据版本,通常发生在需要对数据进行修改的场景中。例如,UPDATE、DELETE、INSERT 等操作就会触发当前读。
- 当前读会通过加锁机制,确保数据的一致性。
典型语句:
SELECT * FROM students WHERE score > 80 FOR UPDATE;
3.2 当前读如何避免幻读?
在 可重复读(REPEATABLE READ)隔离级别下,MySQL 的当前读会引入锁机制(尤其是间隙锁),避免其他事务在同一范围内插入新数据,从而解决幻读问题。
3.3 间隙锁(Gap Lock)的作用
-
定义:
间隙锁是 InnoDB 在事务中为避免幻读问题而引入的一种锁,它锁定的是一个范围(gap),而不仅仅是具体的行。 -
作用:
- 防止其他事务在锁定范围内插入新记录。
- 确保同一事务中的前后查询结果集一致。
-
锁定范围示例:
假设查询条件为WHERE score > 80,当前数据表如下:id name score 1 Alice 85 2 Bob 90 当事务 A 使用当前读时:
SELECT * FROM students WHERE score > 80 FOR UPDATE;InnoDB 会锁定范围
(80, ∞),此时:- 其他事务不能在这个范围内插入新记录,例如
INSERT INTO students (id, name, score) VALUES (3, 'Charlie', 88)会被阻塞。 - 保证当前事务不会读取到“幻影”数据。
- 其他事务不能在这个范围内插入新记录,例如
案例说明:
假设有如下数据:
| id | name | score |
|---|---|---|
| 1 | Alice | 85 |
| 2 | Bob | 90 |
-
事务 A:开启事务,并进行当前读:
START TRANSACTION; SELECT * FROM students WHERE score > 80 FOR UPDATE;返回结果:
id name score 1 Alice 85 2 Bob 90 -
事务 B:试图插入一条数据:
INSERT INTO students (id, name, score) VALUES (3, 'Charlie', 88);结果:事务 B 被阻塞,直到事务 A 提交或回滚。
-
事务 A:再次查询时:
SELECT * FROM students WHERE score > 80 FOR UPDATE;返回的结果仍然是:
id name score 1 Alice 85 2 Bob 90
间隙锁保证了事务 A 的一致性,避免了幻读问题。
间隙锁的局限性:
-
可能引发性能问题:
- 间隙锁会阻止范围内的插入操作,如果锁定范围较大,可能造成事务等待时间过长。
-
事务隔离级别:
间隙锁只在 可重复读 隔离级别下有效,在更低的隔离级别(如读已提交)中不适用。
4. 幻读是否被完全解决?
尽管 可重复读(REPEATABLE READ)隔离级别通过快照读和当前读(间隙锁)在很大程度上避免了幻读,但并不能完全解决所有场景下的幻读问题。以下是详细的分析和示例。
4.1 幻读可能依然存在的场景
在一些复杂事务操作中,即使使用间隙锁,幻读问题依然可能发生。例如:
- 并发事务修改数据的交叉操作:涉及范围查询时,锁范围与实际查询范围不完全匹配。
- 间隙锁未覆盖所有查询条件:事务锁定的范围不能完全避免其他事务插入新的数据。
案例分析:复杂事务下的幻读
假设 students 表当前的状态为:
| id | name | score |
|---|---|---|
| 1 | Alice | 85 |
| 2 | Bob | 90 |
场景描述:
- 事务 A:希望对分数大于 80 的学生记录进行处理。
- 事务 B:在事务 A 操作期间插入了一条符合条件的记录。
具体操作:
-
事务 A:
START TRANSACTION; SELECT * FROM students WHERE score > 80 FOR UPDATE;返回结果:
id name score 1 Alice 85 2 Bob 90 此时,间隙锁锁定了
(80, ∞)范围。 -
事务 B:
在锁定范围之外插入了一条分数为 80 的记录:INSERT INTO students (id, name, score) VALUES (3, 'Charlie', 80);结果:事务 B 的插入成功,因为分数为 80 不在事务 A 锁定的范围
(80, ∞)内。 -
事务 A:
对分数大于等于 80 的记录进行处理:UPDATE students SET score = score + 5 WHERE score >= 80;结果:事务 A 的操作覆盖了事务 B 插入的记录(
Charlie),导致了一种幻读现象。
4.2 为什么幻读未被完全解决?
-
锁范围与查询条件不完全匹配:
间隙锁锁定的范围是基于查询条件计算的,但并不总是能够覆盖所有潜在的记录。 -
复杂的业务场景:
当查询涉及范围变化(如>=、BETWEEN),锁的逻辑变得复杂且难以避免边界情况。
4.3 如何完全解决幻读?
在某些关键场景下,为了避免幻读问题,可以使用更高的隔离级别或额外的手段:
-
可串行化隔离级别(SERIALIZABLE):
- 串行化隔离级别会对所有的读操作加锁,避免了所有类型的幻读问题。
- 代价:显著的性能开销,因为所有事务会变成串行执行。
-
手动加锁:
在事务操作中显式加表锁或行锁,确保范围锁定完全覆盖。 -
业务逻辑约束:
优化业务设计,避免范围查询可能带来的并发插入问题。
具体示例:可串行化避免幻读
事务 A:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION; SELECT * FROM students WHERE score >= 80 FOR UPDATE;
事务 B:
试图插入符合条件的记录:
INSERT INTO students (id, name, score) VALUES (3, 'Charlie', 88);
结果:事务 B 被阻塞,直到事务 A 提交或回滚,避免了幻读现象。
5. 总结
5.1 幻读问题的核心
幻读是事务中一种特殊的数据不一致现象,通常出现在并发环境下。当事务在范围查询中遇到其他事务插入或修改的符合条件的新数据时,可能会导致前后查询结果不一致,这种现象称为幻读。
5.2 如何避免幻读?
-
快照读:
- 使用多版本并发控制(MVCC),通过历史版本实现一致性读,从根本上避免了幻读。
- 快照读适用于查询操作,如
SELECT。
-
当前读:
- 通过加锁(如间隙锁)避免并发插入或修改操作导致幻读问题。
- 当前读适用于更新操作,如
UPDATE、DELETE和带FOR UPDATE的SELECT。
-
间隙锁(Gap Lock):
- 锁定范围而非具体记录,阻止其他事务在锁定范围内插入新数据,从而在可重复读(REPEATABLE READ)隔离级别下避免幻读。
-
可串行化隔离级别(SERIALIZABLE):
- 通过更严格的锁机制,彻底避免了所有类型的幻读问题,但以性能开销为代价。
5.3 幻读是否被完全解决?
- 在可重复读隔离级别下,虽然快照读和间隙锁大大减少了幻读的发生,但仍然存在无法完全覆盖的场景,尤其是复杂的范围查询。
- 要彻底解决幻读问题,需要更高的隔离级别(如可串行化)或优化业务逻辑。
具体实例说明
-
在可重复读隔离级别下:
- 快照读避免了幻读,但不能适用于需要修改数据的场景。
- 当前读通过间隙锁避免了大多数幻读,但可能因锁范围不匹配而产生边界问题。
-
在可串行化隔离级别下:
- 完全阻止了幻读,但事务效率可能显著降低。
5.4 应对幻读的策略
-
选择合适的隔离级别:
- 大多数情况下,可重复读隔离级别(REPEATABLE READ)已经能够满足需求。
- 在需要强一致性时,考虑使用可串行化隔离级别。
-
优化锁机制:
- 根据具体业务需求,合理选择加锁范围,避免过大或过小。
-
业务逻辑调整:
- 通过限制并发操作或使用显式锁,降低幻读发生的概率。
相关文章:
MySQL中的幻读问题
1. 什么是幻读? 幻读是一种数据库事务中可能出现的并发问题,具体表现为:在同一个事务中,前后两次查询的结果集不同,仿佛“幻影”一般,出现了原本不存在的数据。 1.1 具体表现: 现象描述 事务 A…...
AI后端工程师面试题的内容
AI后端工程师面试题主要包括以下几个方面的内容: 一、技术基础和项目经验: 1. 微服务架构的理解和应用:请描述你对微服务架构的理解,并举例说明一个你参与过的微服务项目,阐述你在该项目中扮演的角色和所承…...
MFC工控项目实例三十五读取数据库数据
点击按钮打开文件夹中的数据文件生成曲线 相关代码 void CSEAL_PRESSUREDlg::OnTesReport() {CFileDialog dlgOpen(TRUE/*TRUE打开,FALSE保存*/,0,0,OFN_NOCHANGEDIR|OFN_FILEMUSTEXIST,"All Files(mdb.*)|*.*||",//文件过滤器NULL);CString mdb_1, m…...
OpenWrt -制作ubifs文件系统的固件
目的 创建一个ubifs为文件系统的镜像 将backup目录中的内容打包成ubifs文件系统。 ubifs的分区定义 ubi-backup.cfg 文件内容如下, [backup] modeubi imagenand-ipq6018-single.img vol_id0 vol_typedynamic vol_namebackup [bkver] modeubi imagebackup.ubifs v…...
C++ - 继承
继承的基本概念 继承就是一种代码的复用. 子类通过继承父类, 就能使用父类的变量, 方法. 学生和老师这两种身份, 他们都有共同的属性: 他们都有名称, 年龄, 性别 .... 当然他们也有各种独有的属性, 学生有学号, 老师有工号 .... 对于这些共有的属性, 我们可以将它们提取出来: …...
华为服务器使用U盘重装系统
一、准备工作 下载官方系统(注意服务器CPU的架构是x86-64还是aarch64,不然可能报意想不到的错)制作启动U盘(下载rufus制作工具,注意文件系统选FAT32还是NTFS) 二、安装步骤 将U盘插入USB接口重启服务器…...
网络分层模型( OSI、TCP/IP、五层协议)
1、网络分层模型 计算机网络是一个极其复杂的系统。想象一下最简单的情况:两台连接在网络上的计算机需要相互传输文件。不仅需要确保存在一条传输数据的通路,还需要完成以下几项工作: 发起通信的计算机必须激活数据通路,这包括发…...
前端开发 之 15个页面加载特效上【附完整源码】
文章目录 一:彩球环绕加载特效1.效果展示2.HTML完整代码 二:跷跷板加载特效1.效果展示2.HTML完整代码 三:两个圆形加载特效1.效果展示2.HTML完整代码 四:半环加载特效1.效果展示2.HTML完整代码 五:音乐波动加载特效1.效…...
Spring Boot使用JDK 21虚拟线程
JDK 21引入的虚拟线程(Virtual Threads)是 Project Loom 的一部分,旨在显著简化并发编程并提高 Java 应用的可扩展性。以下是虚拟线程的主要特点: 1. 概念 虚拟线程是轻量级线程,与传统的操作系统线程不同࿰…...
《从0到1常用Map集合核心摘要 + 不深不浅底层核心》
《从0到1常用Map集合核心摘要不深不浅底层核心》 前置知识 什么是键值对 键值对是一种数据结构,键是唯一标识,值是对应数据,用来快速查找信息。例: {"name": "Alice"},键是name,…...
12 设计模式之工厂方法模式
一、什么是工厂方法模式? 1.定义 在软件开发中,设计模式 是解决常见软件设计问题的最佳实践。而 工厂方法模式(Factory Method Pattern) 作为创建型设计模式之一,常常被用来解决对象创建问题。它通过将对象的创建交给…...
spaCy 入门与实战:强大的自然语言处理库
spaCy 入门与实战:强大的自然语言处理库 spaCy 是一个现代化、工业级的自然语言处理(NLP)库,以高效、易用和功能丰富著称。它被广泛应用于文本处理、信息提取和机器学习任务中。本文将介绍 spaCy 的核心功能,并通过一…...
python包的管理和安装——笔记
1.列出包 pip list pip freeze 用这2个可以查看当前python 下所有的包和版本,还有下载地址 如果只是想导出当前的环境 可以用 2.安装pipreqs pip install pipreqs,pipreqs ./可以导出当前项目的包这个包 遇到编码报错 pipreqs ./ --encodingutf8 p…...
Vue前端页面内嵌套本项目iframe窗口的通信传输方式
一、目的 想要在iframe中使用本项目页面、并能够与其父页面组件实现实时通信。Vue前端页面内嵌套本项目iframe窗口的通信传输方式-星林社区 https://www.jl1mall.com/forum/PostDetail?postId20241202172800023969 二、iframe通信方式 1.接收消息 页面需要监听 message 事件…...
【WEB开发.js】addEventListener事件监听器的绑定和执行次数的问题(小心踩坑)
假设我们有一个按钮,用户点击该按钮后,会选择一个文件,且我们希望每次点击按钮时只触发一次文件处理。下面我会给你一个简单的例子,展示放在函数内部和放在函数外部的区别。 1. 将事件监听器放在函数内部(问题的根源&…...
用于LiDAR测量的1.58um单芯片MOPA(一)
--翻译自M. Faugeron、M. Krakowski1等人2014年的文章 1.简介 如今,人们对高功率半导体器件的兴趣日益浓厚,这些器件主要用于遥测、激光雷达系统或自由空间通信等应用。与固态激光器相比,半导体器件更紧凑且功耗更低,这在低功率供…...
【GPT】代谢概念解读
以下是对代谢中分解代谢和合成代谢两个概念的深入解读,用简单易懂的方式展开说明: 1. 分解代谢(Catabolism) 什么是分解代谢? 分解代谢是身体把大分子“拆开”的过程。就像把一个三明治分解成面包片、肉片和菜叶&#…...
Devops-git篇-01-git环境配置
环境配置 设置用户签名 配置用户名: git config --global user.name 你的用户名 配置邮箱: git config --global user.email 注册的邮箱 配置好之后,可以用git config --global --list命令查看配置是否OK $ git config --global --list u…...
STM32 HAL库开发学习1.STM32CubeMX 新建工程
STM32 HAL库开发学习1.STM32CubeMX 新建工程 一、 STM32 CubeMX 下载二、CubeMX 功能介绍1. 固件包路径设置2. 新建工程 三、创建项目实例1. 新建项目2. GPIO 管脚设置3. GPIO 窗口配置4. 调试设置5. 时钟配置6. 项目管理(1)项目信息(2&#…...
JS学习(2)(浏览器执行JS过程、JS的ECMAScript、DOM、BOM)
目录 一、浏览器如何执行JS? (1)浏览器主要的组成部分。 1、渲染引擎。 2、JS引擎。 (2)演示。 二、JS的组成。 (1)JS主要由三部分组成。 1、JS基础。 2、JS-API。 (2)EC…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...
算法刷题-回溯
今天给大家分享的还是一道关于dfs回溯的问题,对于这类问题大家还是要多刷和总结,总体难度还是偏大。 对于回溯问题有几个关键点: 1.首先对于这类回溯可以节点可以随机选择的问题,要做mian函数中循环调用dfs(i&#x…...
Git 命令全流程总结
以下是从初始化到版本控制、查看记录、撤回操作的 Git 命令全流程总结,按操作场景分类整理: 一、初始化与基础操作 操作命令初始化仓库git init添加所有文件到暂存区git add .提交到本地仓库git commit -m "提交描述"首次提交需配置身份git c…...
代理服务器-LVS的3种模式与调度算法
作者介绍:简历上没有一个精通的运维工程师。请点击上方的蓝色《运维小路》关注我,下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 我们上一章介绍了Web服务器,其中以Nginx为主,本章我们来讲解几个代理软件:…...
