【Java面试篇】什么是脏读、不可重复读、幻读?MySQL 是如何解决这些问题的
文章目录
题目:什么是脏读、不可重复读、幻读?MySQL 是如何解决这些问题的
适合:在职 / 跳槽 / 转行 | 级别:Java 初级开发 | 难度:⭐⭐⭐⭐ | 频率:极高
前言
关注公众号“知识汲取者”【Java面试题】合集,可以每天抽空五分钟,趁着坐车、蹲坑、摸鱼等琐碎时间看一看,一来可以扎实基本功,二来可以随时熟悉面试题,让我们始终保持拥有随时可面的状态,时刻保持危机意识。
本专栏适合人群:
1. 在职的 Java 开发
2. 准备或正在跳槽的 Java 开发
3. 未来想从事 Java 开发
一、面试原题
请详细解释什么是脏读、不可重复读、幻读?MySQL 针对这三大读问题,分别采用了哪些方案来解决?核心原理是什么?
二、先吃透:三大读问题(定义+场景+核心)
三大读问题的本质是:事务并发执行时,数据一致性被破坏,MySQL 通过“隔离级别+锁机制+MVCC”组合,针对性解决这些问题,先明确三个问题的核心区别,避免面试混淆。
1. 脏读(Dirty Read)—— 最危险的读问题
定义:一个事务读取到了另一个事务未提交的数据(中间数据),若后续该事务回滚,当前读取到的数据就会变成“无效脏数据”,导致业务逻辑出错。
通俗场景:
-
事务A:查询用户余额为100元
-
事务B:修改用户余额为200元(未提交)
-
事务A:再次查询,读到余额200元(脏数据)
-
事务B:发现错误,执行回滚,余额恢复为100元
-
事务A:基于200元的脏数据做业务操作(如转账),出现逻辑错误
核心痛点:读取到“未提交、可回滚”的无效数据,直接影响业务正确性。
2. 不可重复读(Non-Repeatable Read)
定义:同一个事务内,先后两次查询同一条记录,中间被其他事务修改并提交,导致两次查询结果不一致(聚焦“单条记录的修改”)。
通俗场景:
-
事务A:查询用户A的年龄为25岁
-
事务B:修改用户A的年龄为26岁,提交事务
-
事务A:再次查询用户A的年龄,变成26岁,前后读取结果不一致
核心痛点:同一事务内,同一条数据的读取结果不统一,影响数据一致性(但数据本身是“已提交”的有效数据)。
3. 幻读(Phantom Read)
定义:同一个事务内,先后两次做范围查询/分页查询,中间被其他事务插入/删除数据并提交,导致两次查询的“记录条数、范围匹配结果”不一致,像出现幻觉一样(聚焦“范围数据的增删”)。
通俗场景:
-
事务A:查询id在1-10之间的用户,查到3条数据
-
事务B:插入一条id=5的用户,提交事务
-
事务A:再次查询id在1-10之间的用户,查到4条数据,多出一条“幻觉数据”
核心痛点:同一事务内,范围查询的结果条数不统一,容易导致分页错乱、统计错误。
三者极简区分(面试必背)
-
脏读:读“未提交”的数据(数据无效)
-
不可重复读:读“已提交”的数据,但单条记录被修改(数据有效,结果不一致)
-
幻读:读“已提交”的数据,但范围数据被增删(数据有效,条数不一致)
三、核心重点:MySQL 如何解决三大读问题?
MySQL 解决三大读问题的核心逻辑是:通过“事务隔离级别”划定并发边界,配合“MVCC多版本并发控制”和“锁机制”,针对性阻断问题产生(InnoDB 引擎才支持,MyISAM 不支持事务,无法解决)。
核心方案:隔离级别 + MVCC + 锁(临键锁/间隙锁/行锁),下面分问题、分隔离级别详细说明。
1. 解决脏读:靠“隔离级别”阻断未提交数据读取
脏读的根源是“读取未提交数据”,MySQL 通过提升隔离级别,禁止读取未提交数据,彻底解决脏读:
-
读未提交(Read Uncommitted):允许读未提交数据 → 存在脏读(不解决)
-
读已提交(RC)及以上级别:仅允许读取“已提交”的数据 → 彻底解决脏读
核心原理:RC、RR、串行化级别中,事务只能读取其他事务“commit”后的的数据,未提交的事务数据会被隔离,无法被其他事务读取,从根源杜绝脏读。
2. 解决不可重复读:靠“MVCC 快照隔离”
不可重复读的根源是“同一事务内,多次查询复用不同快照”,MySQL 主要通过 MVCC(多版本并发控制)解决,核心针对 RR 隔离级别(默认级别):
MVCC 核心原理(大白话):
-
RR 级别下,事务第一次查询时,生成一份“一致性快照”(记录当前数据库的版本状态)
-
事务后续所有查询,都复用这份快照,不读取其他事务后续提交的修改数据
-
即使其他事务修改了数据并提交,当前事务看到的依然是第一次快照的数据,确保“重复读”一致
补充说明:
-
RC 级别:每次查询都生成新快照,无法解决不可重复读(会读到其他事务已提交的修改)
-
串行化级别:事务串行执行,不存在并发修改,也能解决不可重复读(但性能极差)
3. 解决幻读:靠“MVCC + 临键锁”组合(RR 级别核心方案)
幻读的根源是“范围查询时,其他事务插入/删除数据”,MySQL 无法彻底根除幻读(除串行化),但在默认 RR 级别,通过“MVCC + 临键锁”极大规避幻读,分两种场景:
场景1:普通快照读(select * from ...)
靠 MVCC 解决:事务复用第一次生成的快照,其他事务插入/删除的新数据,不在当前快照中,所以查询不到,规避幻读。
场景2:当前读(select ... for update / update / delete)
靠“临键锁(Next-Key Lock)”解决:
-
临键锁 = 行锁 + 间隙锁
-
行锁:锁住查询到的已存在记录
-
间隙锁:锁住“查询范围的间隙”(比如查询id 1-10,会锁住id 0-1、1-2、...、9-10、10-11的间隙)
-
间隙锁的作用:禁止其他事务在该间隙内插入新数据,从物理层面堵住幻读的产生
补充说明
-
RC 级别:无间隙锁,每次查询刷新快照,无法规避幻读
-
串行化级别:事务串行执行,完全禁止并发插入/删除,彻底解决幻读(但并发极低,不推荐线上使用)
-
RR 级别:不是100%彻底解决幻读(特殊业务逻辑下可能出现),但能覆盖99%的业务场景,是性能和一致性的最优解
四、隔离级别与问题解决对照表(面试必背)
| 事务隔离级别 | 脏读 | 不可重复读 | 幻读 | 核心解决手段 |
|---|---|---|---|---|
| 读未提交(Read Uncommitted) | 存在 | 存在 | 存在 | 无(几乎不用) |
| 读已提交(RC) | 解决 | 存在 | 存在 | 禁止读取未提交数据 |
| 可重复读(RR)默认 | 解决 | 解决 | 大部分规避 | MVCC 快照隔离 + 临键锁 |
| 串行化(Serializable) | 解决 | 解决 | 完全解决 | 事务串行执行,禁止并发 |
五、面试30秒背诵版(直接套用)
- 脏读是读到其他事务未提交的脏数据,MySQL 靠 RC 及以上隔离级别禁止读取未提交数据解决;
- 不可重复读是同一事务多次查同一条记录,被其他事务修改提交导致结果不一致,MySQL 靠 RR 级别 MVCC 快照隔离解决;
- 幻读是范围查询被其他事务增删数据,导致条数不一致,MySQL 靠 RR 级别 MVCC + 临键锁(行锁+间隙锁)规避,串行化能彻底解决但性能差。
- MySQL 默认 RR 级别,是性能和数据一致性的最优选择。
六、小测验(巩固记忆)
-
MySQL 解决脏读的核心手段是什么?RC 级别能解决不可重复读吗?
-
MVCC 主要解决什么读问题?临键锁的作用是什么?
-
RR 级别为什么不能100%彻底解决幻读?
点赞 + 收藏,每天 5 分钟,面试稳稳上岸 ✨
更多推荐
所有评论(0)