Mysql 幻读详解

        我们来详细地聊一聊 MySQL InnoDB 中的“幻读”(Phantom Read)问题。这是一个在数据库事务隔离中非常核心且有时令人困惑的概念。

我会从定义、例子、原因以及解决方案几个方面来彻底讲清楚。

1. 什么是幻读?

官方定义:幻读指的是在一个事务内,相同的查询在不同时间执行,返回了不同数量的行

这听起来和“不可重复读”很像,但它们有关键区别:

  • 不可重复读 (Non-Repeatable Read):侧重于同一行的数据内容被修改或删除。(例如:你第二次查询时,某行的薪水从10000变成了12000)。

  • 幻读 (Phantom Read):侧重于新的行被插入(或删除),导致结果集的行数发生了变化。(针对结果集的数量变化,例如:你第一次查询有10条记录,第二次查询却冒出了11条)。

简单比喻

  • 不可重复读:你碗里的一块红烧肉被别人咬了一口(数据内容变了)。

  • 幻读:你正准备夹碗里最后一块红烧肉时,别人突然又往碗里加了一块新的肉(数据行数变了)。


2. 幻读发生的场景与例子

幻读发生的根本原因是:在“可重复读(REPEATABLE READ)”及以下隔离级别中,普通的一致性读(快照读)无法阻止其他事务插入新的、满足当前查询条件的数据

我们来看一个经典的例子。

表结构

CREATE TABLE `employee` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(50) NOT NULL,`salary` int(11) DEFAULT NULL,PRIMARY KEY (`id`),KEY `idx_salary` (`salary`)
) ENGINE=InnoDB;INSERT INTO `employee` (`name`, `salary`) VALUES
('Alice', 8000),
('Bob', 9000),
('Charlie', 10000);

时间线

时间事务A (隔离级别:RR)事务B
T1START TRANSACTION;
T2SELECT * FROM employee WHERE salary > 8000;
结果
Bob, 9000
Charlie, 10000
(2 rows)
START TRANSACTION;
T3INSERT INTO employee (name, salary) VALUES ('David', 9500);
COMMIT; <!-- 事务B提交 -->
T4SELECT * FROM employee WHERE salary > 8000;
结果
Bob, 9000
Charlie, 10000
(仍然是2 rows!)
(这里没有幻读,因为RR级别通过MVCC提供了快照)
T5UPDATE employee SET salary = 8888 WHERE salary > 8000;
(注意:这个更新操作是当前读,会看到事务B已提交的修改)
T6SELECT * FROM employee WHERE salary > 8000;
结果
Bob, 8888
Charlie, 8888
David, 8888
(3 rows! 幻读出现了!)

例子分析

  1. T2时刻:事务A第一次查询,得到2条记录。

  2. T4时刻:事务A第二次普通查询(快照读)。由于InnoDB的MVCC(多版本并发控制)机制,它会读取事务开始时的数据快照,所以看不到事务B新插入的 David(9500)。此时还没有幻读。

  3. T5时刻:关键点来了!事务A执行了一个UPDATE操作。UPDATE/DELETE/INSERT 这类写操作会使用“当前读”(Current Read),它会读取数据库中最新的、已提交的数据。因此,它看到了事务B插入的 David(9500) 这条记录,并将其薪水也更新为8888。

  4. T6时刻:事务A再次查询。因为之前的UPDATE操作属于当前事务的修改,所以MVCC规则允许它看到自己的修改。于是,它神奇地看到了三条记录!幻读就在这一刻发生了

这个例子展示了InnoDB中幻读最典型的特征:即使是在默认的RR隔离级别下,先快照读,再当前读进行写操作,可能会意外地影响新插入的行,从而导致数据不一致。


3. 解决方案:Next-Key Lock 锁机制

InnoDB引擎为了解决幻读问题,在“可重复读(REPEATABLE READ)”隔离级别下就引入了一种叫做 Next-Key Lock 的锁机制。它实际上是 记录锁(Record Lock) 和 间隙锁(Gap Lock) 的结合。

  • 记录锁 (Record Lock):锁住索引上的某一条具体记录。

  • 间隙锁 (Gap Lock):锁住索引记录之间的“间隙”,防止在这个间隙内插入新的数据。它是一个左开右开的区间 (a, b)

  • 临键锁 (Next-Key Lock):是记录锁 + 间隙锁的结合。它锁住一条记录和它前面的间隙。它是一个左开右闭的区间 (a, b]

如何解决幻读?

在上面的例子中,如果事务A在第一次查询时,就对 salary > 8000 这个条件加上了锁,那么事务B的插入操作就会被阻塞,从而杜绝幻读。

让我们重演时间线,但这次事务A加锁查询

时间事务A (加锁查询)事务B
T1START TRANSACTION;
T2SELECT * FROM employee WHERE salary > 8000 FOR UPDATE;
(FOR UPDATE 会给查询结果加Next-Key Lock)
结果:2 rows
START TRANSACTION;
T3INSERT INTO employee (name, salary) VALUES ('David', 9500);
(这条语句会被阻塞,一直等待事务A释放锁!)
T4SELECT ... FOR UPDATE; (再次查询,结果一致)...(阻塞中)...
T5COMMIT; (提交事务,释放锁)...(阻塞结束)...
T6(此时事务B才能成功插入)

发生了什么?

当事务A执行 SELECT ... FOR UPDATE 时,InnoDB会为其加Next-Key Lock。假设 salary 上有二级索引 idx_salary,它可能会锁住以下区间:

  • 锁住 (8000, 9000] 这个Next-Key Lock(锁住9000这条记录和它前面的间隙)。

  • 锁住 (9000, 10000] 这个Next-Key Lock(锁住10000这条记录和它前面的间隙)。

  • 锁住 (10000, +∞] 这个Next-Key Lock(锁住正无穷的上界)。

事务B试图插入 salary = 9500 的记录,这个值落在被事务A锁住的 (9000, 10000] 间隙内,因此插入操作会被阻塞,直到事务A提交释放锁。这样就彻底防止了幻读的发生。


总结与最佳实践

特性说明
幻读本质同一事务内,两次查询结果集行数不一致, due to 其他事务的插入删除操作。
InnoDB的默认防御REPEATABLE READ隔离级别下,InnoDB通过 Next-Key Lock 机制来防止幻读。
何时会发生幻读即使是在RR级别下,如果你只是进行普通的快照读(SELECT),然后基于此进行当前读的写操作(UPDATE/INSERT/DELETE),仍然可能遇到幻读。快照读不加锁是根源。
彻底解决方法在需要绝对保证数据一致性的关键操作中,使用 加锁读
1. SELECT ... FOR UPDATE; (加写锁,阻塞其他事务的写和加锁读)
2. SELECT ... LOCK IN SHARE MODE; (加读锁,阻塞其他事务的写)
这些语句会在符合条件的索引上加Next-Key Lock,从而阻止其他事务在锁定区间内插入新数据。
终极方案将事务隔离级别提升至 SERIALIZABLE。在这个级别下,所有的读操作都会默认加上类似 LOCK IN SHARE MODE 的锁,幻读自然不会发生,但这是以牺牲并发性能为代价的,一般不建议使用。

核心要点:MySQL InnoDB 在 RR 级别下已经通过 Next-Key Lock 很大程度上解决了幻读问题。但你需要清楚地知道,只有在你的查询语句确实需要加锁(例如使用了 FOR UPDATE)或者涉及写操作时,Next-Key Lock 才会生效。单纯的快照读是无法完全避免幻读的潜在影响的。

推荐一个非常好用的工具集合:在线工具集合 - 您的开发助手

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

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

相关文章

如何生成 GitHub Token(用于 Hexo 部署):保姆级教程+避坑指南

如何生成 GitHub Token&#xff08;用于 Hexo 部署&#xff09;&#xff1a;保姆级教程避坑指南 前置说明&#xff1a;为什么需要 GitHub Token&#xff1f; 在使用 Hexo 部署博客到 GitHub Pages 时&#xff0c;你可能会遇到「密码验证失败」或「需要双重验证」的问题——这…

常用加密算法之 AES 简介及应用

相关系列文章 常用加密算法之 SM4 简介及应用常用加密算法之 RSA 简介及应用 引言 AES&#xff08;Advanced Encryption Standard&#xff0c;高级加密标准&#xff09;是一种​​广泛使用的对称分组加密算法​​&#xff0c;它使用相同的密钥进行加密和解密操作&#xff0c…

Java面试问题记录(一)

一、Java 核心基础与进阶1、我们知道 Java 中存在 “值传递” 和 “引用传递” 的说法&#xff0c;你能结合具体例子&#xff0c;说明 Java 到底是值传递还是引用传递吗&#xff1f;这背后涉及到 JVM 中哪些内存区域的交互&#xff1f;Java中只有值传递&#xff0c;不存在引用传…

Redis 主从复制、哨兵与 Cluster 集群部署

文章摘要 本文基于 VMware 虚拟机环境&#xff0c;详细讲解 Redis 高可用架构的核心组件与部署流程&#xff0c;涵盖三大核心模块&#xff1a;Redis 主从复制&#xff08;实现数据备份与读写分离&#xff09;、Redis 哨兵&#xff08;基于主从复制实现故障自动转移&#xff0c;…

ElementUI 中 validateField 对部分表单字段数组进行校验时多次回调问题

目录 方案一&#xff1a;循环调用 Promise.all 合并结果 方案二&#xff1a;直接传入数组字段 总结 在实际业务中&#xff0c;我们有时只需要对表单的部分字段进行校验。ElementUI 提供的 validateField 方法支持单个字段&#xff0c;也支持字段数组&#xff0c;但在使用时…

Visual Studio 2026 震撼发布!AI 智能编程时代正式来临

Visual Studio 2026 震撼发布&#xff01;AI 智能编程时代正式来临 Visual Studio 2026 Insider图标 开发者们的开发环境即将迎来前所未有的智能革命&#xff0c;微软用Visual Studio 2026 重新定义了编码体验。 2025年9月10日&#xff0c;微软正式推出了Visual Studio 2026 In…

Gamma AI:高效制作PPT的智能生成工具

你有没有过这种崩溃时刻&#xff1f;领导让你下午交一份产品介绍 PPT&#xff0c;你打开模板网站翻了半小时没找到合适的&#xff0c;好不容易选了个模板&#xff0c;又得手动调整文字间距、搭配图片&#xff0c;光是把数据做成图表就花了一小时&#xff0c;最后赶出来的 PPT 还…

Python副业新玩法:用Flask搭小程序后端,躺赚被动收入的秘密

凌晨1点&#xff0c;林浩合上电脑时&#xff0c;手机弹出一条微信消息——是上周帮一家社区水果店搭的小程序后端&#xff0c;商家发来了当月的服务费到账提醒。他靠在椅背上笑了&#xff1a;这是这个月第8笔“睡后收入”&#xff0c;加起来刚好覆盖了下个月的房贷。半年前&…

基于PyQt5和阿里云TTS的语音合成应用开发实战[附源码】

项目概述 本文将详细介绍一个基于PyQt5图形界面框架和阿里云TTS(Text-to-Speech)服务的语音合成桌面应用程序的开发过程。该应用提供了完整的文字转语音功能,包括多音色选择、参数调节、实时试听、语速调节和音频下载等特性。 技术栈 前端界面: PyQt5 语音合成: 阿里云TTS服…

基于esp32c3 rust embassy 的墨水屏程序

EPD Reader 基于ESP32-C3的电子墨水屏阅读器&#xff0c;支持ap 配网、sntp 时间同步、txt阅读、天气预报、显示节假日信息、农历显示、自动休眠、web配置等功能。这是在另一个项目 一个rust embassy esp32c3 的练习项目-CSDN博客的基础上修改的 。 界面比较粗糙&#xff0c;以…

Spring 单例测试及线程安全

创建一个账户类 package com.duanhw.demo22.account;import org.springframework.beans.factory.annotation.Value;//Service public class AccountService {Value("1000")private Integer balance;//存款public void deposit(Integer amount){int newbalance balanc…

【vue】组件宽度调整失效后,调整的方法

父容器布局限制 若组件放置在栅格布局&#xff08;如display: grid&#xff09;或弹性容器中&#xff0c;父元素的宽度限制可能导致子组件宽度失效。解决方案是为父容器设置明确的宽度&#xff0c;或通过百分比布局实现自适应16。例如&#xff1a; <div style"width:…

Java 在Word 文档中插入页眉页脚:一份实用的编程指南

在现代企业应用中&#xff0c;Java 开发者经常需要处理各种文档操作&#xff0c;其中对 Word 文档的自动化处理尤为常见。无论是生成报告、合同还是其他商业文档&#xff0c;页眉页脚作为文档结构的重要组成部分&#xff0c;承载着公司 Logo、页码、版权信息等关键内容。手动添…

深入解析Dart虚拟机运行原理

Dart虚拟机运行原理 一、Dart虚拟机 1.1 引言 Dart VM是一种虚拟机&#xff0c;为高级编程语言Dart提供执行环境&#xff0c;但这并意味着Dart在D虚拟机上执行时&#xff0c;总是采用解释执行或者JIT编译。 例如还可以使用Dart虚拟机的AOT管道将Dart代码编译为机器代码&#xf…

光谱相机在AI眼镜领域中的应用

一、核心应用场景‌健康监测系统‌‌实时生理指标分析‌&#xff1a;通过眼周皮肤光谱特征&#xff0c;监测血氧(SpO₂)和血红蛋白变化&#xff0c;精度可达2%‌血糖无创检测‌&#xff1a;近红外光谱(900-1700nm)分析泪液成分&#xff0c;临床测试相关系数R0.87‌疲劳度评估‌…

如何通过url打开本地文件文件夹

安装部署 https://github.com/jixn-hu/notion_link_opener 这是我自己开发的一个后端服务&#xff0c;要一直开着 部署好后 会打开一个前端页面填下好你文件或者文件夹 点击生成短链就可以直接打开本地的文件夹了

第一篇:如何在数组中操作数据【数据结构入门】

记录以下自己重温数据结构的笔记&#xff0c;附带自己实现的C代码&#xff0c; 其中部分Python代码是网上教程里的&#xff0c;顺手粘贴过来&#xff0c;做一对比/ &#xff08;Python确实简洁&#xff0c;但是C更好理解不是吗哈哈哈&#xff09;数组的定义 数组&#xff1a;线…

基于STM32的单片机开发复盘

硬件介绍 底盘&#xff1a;幻尔阿克曼底盘&#xff1b;2个直流霍尔电机、1个PWM舵机开发板&#xff1a;幻尔Ros Controller V1.2&#xff08;STM32F407VET6&#xff09;电源&#xff1a;因为是学习阶段&#xff0c;没有配电池&#xff0c;使用120W可调电源&#xff08;3V~12V&a…

面试常问:注册中心宕机,远程调用还能成功吗?

在微服务架构里&#xff0c;注册中心&#xff08;像 Nacos、Eureka、Consul 等&#xff09;是服务发现与治理的核心。可要是注册中心突然宕机&#xff0c;微服务间的远程调用还能顺利进行吗&#xff1f;这是面试时很常被问到的问题&#xff0c;下面我们就来深入剖析。一、远程调…

《用 Python 和 Matplotlib 绘制折线图:从入门到实战的可视化指南》

《用 Python 和 Matplotlib 绘制折线图:从入门到实战的可视化指南》 一、引言:数据可视化的力量,从一张折线图开始 在我多年的开发与教学经历中,最常被问到的问题之一是:“如何让数据更直观?”我的答案始终如一:用图说话。而在众多图表类型中,折线图以其简洁、清晰的…