MySQL 脏读、幻读、不可重复读是什么?
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
面试考察点
-
基础掌握度:面试官不仅仅是想知道三个概念的定义,更是想知道你是否理解它们产生的根本原因 —— 并发事务之间的数据干扰,以及它们之间的本质区别。
-
隔离级别理解:考察你是否清楚 MySQL 的四种事务隔离级别,以及每种隔离级别能解决哪些并发问题。
-
实践应用能力:能否根据业务场景选择合适的隔离级别,是否了解 MVCC(多版本并发控制)如何解决这些问题。
核心答案
脏读、不可重复读、幻读是 数据库并发事务 中三种典型的数据不一致问题:
| 问题 | 定义 | 关注点 | 严重程度 |
|---|---|---|---|
| 脏读 | 读到其他事务未提交的数据 | 数据被回滚 | ⭐⭐⭐ 最严重 |
| 不可重复读 | 同一事务内,两次读取同一行数据结果不同 | 数据被修改/删除 | ⭐⭐ 中等 |
| 幻读 | 同一事务内,两次查询结果行数不同 | 数据被新增 | ⭐ 较轻 |
一句话总结:脏读是读了未提交的数据;不可重复读是同一条数据变了;幻读是结果集行数变了。
深度解析
一、三种问题详解
上图展示了脏读的发生过程:
- 时间点 T3:事务 B 将
id=1的age从 20 改为 25,但尚未提交 - 时间点 T4:事务 A 读取该行数据,得到
age=25 - 时间点 T5:事务 B 回滚了,数据恢复为 20
- 问题:事务 A 读到的
age=25是 "脏数据",因为事务 B 根本没提交
脏读危害:基于未提交数据做业务判断,可能导致严重的逻辑错误。
上图展示了不可重复读的发生过程:
- 时间点 T2:事务 A 第一次读取,
age=20 - 时间点 T4~T5:事务 B 修改了这条数据并提交
- 时间点 T6:事务 A 再次读取同一行,
age=25 - 问题:同一事务内,两次读取同一数据,结果不一致
与脏读的区别:不可重复读读到的是已提交的数据,不是脏数据。
上图展示了幻读的发生过程:
- 时间点 T2:事务 A 查询
age > 20的记录,返回 2 条 - 时间点 T4~T5:事务 B 新增了一条
age=30的记录并提交 - 时间点 T6:事务 A 再次用相同条件查询,返回 3 条
- 问题:同一事务内,两次相同查询,结果行数不同,仿佛出现了 "幻影"
与不可重复读的区别:
- 不可重复读强调 同一条数据被修改(UPDATE/DELETE)
- 幻读强调 结果集行数变化(INSERT/DELETE)
二、四种隔离级别
隔离级别详解:
-
READ UNCOMMITTED(读未提交)
- 最低隔离级别
- 可能发生脏读、不可重复读、幻读
- 几乎不使用
-
READ COMMITTED(读已提交,RC)
- 只能读取已提交的数据
- 解决了脏读,但可能发生不可重复读、幻读
- Oracle、SQL Server 默认级别
-
REPEATABLE READ(可重复读,RR)
- MySQL InnoDB 默认隔离级别
- 保证同一事务内多次读取同一数据结果一致
- 解决了脏读、不可重复读
- InnoDB 通过 MVCC + Next-Key Lock 也解决了幻读
-
SERIALIZABLE(串行化)
- 最高隔离级别
- 强制事务串行执行
- 解决所有问题,但性能最差
三、MySQL 如何解决这些问题
MVCC 解决不可重复读:
-- RR 级别下,MVCC 保证可重复读
-- 事务 A
BEGIN;
SELECT * FROM users WHERE id = 1; -- age=20,生成 Read View
-- 此时事务 B 修改并提交
-- UPDATE users SET age=25 WHERE id=1; COMMIT;
SELECT * FROM users WHERE id = 1; -- 仍然读到 age=20
-- 因为 MVCC 根据事务 A 的 Read View,只能看到历史版本
COMMIT;
Next-Key Lock 解决幻读:
-- RR 级别下,当前读使用 Next-Key Lock
-- 事务 A
BEGIN;
SELECT * FROM users WHERE age > 20 FOR UPDATE;
-- 锁定 age > 20 的所有行,以及 (20, +∞) 的间隙
-- 此时事务 B 尝试插入
-- INSERT INTO users(age) VALUES(30); -- 被阻塞!
COMMIT; -- 事务 A 提交后,事务 B 才能插入
四、对比总结
面试高频追问
-
MySQL 默认隔离级别是什么?如何解决幻读?
- 默认是
REPEATABLE READ(可重复读) - 通过 MVCC 解决快照读的幻读
- 通过 Next-Key Lock(行锁 + 间隙锁)解决当前读的幻读
- 默认是
-
RC 和 RR 的区别?
RC:每次SELECT生成新的Read View,能看到其他事务已提交的修改RR:第一次SELECT生成Read View,后续复用,保证可重复读
-
什么是 MVCC?
- 多版本并发控制,每行数据保存版本链(通过
undo log) - 读操作根据
Read View判断哪个版本对自己可见 - 实现 "读不阻塞写,写不阻塞读"
- 多版本并发控制,每行数据保存版本链(通过
-
什么时候用 SERIALIZABLE?
- 需要严格数据一致性的场景(如金融交易)
- 一般不推荐使用,性能太差
常见面试变体
- "MySQL 有哪些事务隔离级别?"
- "RR 级别能完全解决幻读吗?"
- "MVCC 是怎么实现的?"
- "什么是 Next-Key Lock?"
记忆口诀
脏读未提交,数据不可信; 不可重复读,同条数据变; 幻读多或少,结果行数变。 RC 解脏读,RR 解后俩,串行全解决。
总结
脏读是读到未提交的数据(最严重);不可重复读是同一事务内同一行数据变了(UPDATE/DELETE);幻读是同一事务内查询结果行数变了(INSERT/DELETE)。MySQL 默认使用 REPEATABLE READ 隔离级别,通过 MVCC 解决不可重复读,通过 MVCC + Next-Key Lock 解决幻读。