题目:什么是脏读、不可重复读、幻读?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秒背诵版(直接套用)

  1. 脏读是读到其他事务未提交的脏数据,MySQL 靠 RC 及以上隔离级别禁止读取未提交数据解决;
  2. 不可重复读是同一事务多次查同一条记录,被其他事务修改提交导致结果不一致,MySQL 靠 RR 级别 MVCC 快照隔离解决;
  3. 幻读是范围查询被其他事务增删数据,导致条数不一致,MySQL 靠 RR 级别 MVCC + 临键锁(行锁+间隙锁)规避,串行化能彻底解决但性能差。
  4. MySQL 默认 RR 级别,是性能和数据一致性的最优选择。

六、小测验(巩固记忆)

  1. MySQL 解决脏读的核心手段是什么?RC 级别能解决不可重复读吗?

  2. MVCC 主要解决什么读问题?临键锁的作用是什么?

  3. RR 级别为什么不能100%彻底解决幻读?


点赞 + 收藏,每天 5 分钟,面试稳稳上岸 ✨

更多推荐