news 2026/4/22 10:56:33

java面试必问17:MySQL 事务隔离级别:从脏读到串行化,读懂就变高手

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
java面试必问17:MySQL 事务隔离级别:从脏读到串行化,读懂就变高手

MySQL 事务隔离级别:从脏读到串行化,一篇讲透

面试官:“MySQL 的事务隔离级别有哪些?默认是什么?”
你:“四个级别:读未提交、读已提交(RC)、可重复读(RR,MySQL 默认)、串行化。它们分别解决脏读、不可重复读、幻读问题。”
面试官:“那 RR 级别下如何解决幻读?和 RC 有什么区别?”
你:“……”

很多人能背出级别名称,但一追问“MVCC 怎么实现”“间隙锁是什么”就含糊了。本文从理论基础到 InnoDB 实现,彻底讲透事务隔离级别。


一、事务的四大特性(ACID)

在讲隔离级别之前,先回顾事务的基本特性:

特性含义
原子性(Atomicity)事务中的操作要么全部成功,要么全部失败
一致性(Consistency)事务前后数据状态保持一致
隔离性(Isolation)多个事务并发执行时,相互隔离不干扰
持久性(Durability)事务提交后,数据永久保存

隔离级别就是用来控制隔离性的强弱程度。隔离级别越高,数据一致性越好,但并发性能越低。


二、三个并发问题

在并发事务中,可能出现以下三种读问题:

问题含义示例
脏读(Dirty Read)读到另一个事务未提交的数据事务A修改了数据但未提交,事务B读取到,然后事务A回滚,B读到的是脏数据
不可重复读(Non-Repeatable Read)同一事务内两次读取同一条记录,结果不同(因为其他事务更新了该记录)事务A第一次读取某行值为10,事务B修改为20并提交,事务A再次读取变成20
幻读(Phantom Read)同一事务内两次查询结果集的行数不同(因为其他事务插入了新行)事务A查询id>10的记录有3条,事务B插入一条id=11并提交,事务A再次查询变成4条

注意区分:

  • 不可重复读:针对同一条记录的修改。
  • 幻读:针对一批记录的插入或删除(行数变化)。

三、四种隔离级别

隔离级别脏读不可重复读幻读并发性能
读未提交(Read Uncommitted)可能可能可能最高
读已提交(Read Committed)不可能可能可能较高
可重复读(Repeatable Read,默认)不可能不可能理论上可能(InnoDB通过间隙锁解决)中等
串行化(Serializable)不可能不可能不可能最低

MySQL InnoDB 默认使用可重复读(RR),并且通过MVCC + 间隙锁解决了幻读问题(后面细说)。


四、各隔离级别详解

1. 读未提交(Read Uncommitted)

事务可以读取其他事务未提交的修改。这是最低级别,几乎从不使用。

问题:脏读。
示例

-- 事务ASTARTTRANSACTION;UPDATEaccountSETbalance=balance-100WHEREid=1;-- 未提交-- 事务B(隔离级别读未提交)STARTTRANSACTION;SELECTbalanceFROMaccountWHEREid=1;-- 读到已扣减但未提交的值-- 如果事务A回滚,事务B读到的就是脏数据

适用场景:无,除非你能接受脏数据。

2. 读已提交(Read Committed,RC)

事务只能读取其他事务已经提交的修改。这是很多数据库(如 PostgreSQL、Oracle)的默认级别。

解决的问题:脏读。
仍存在的问题:不可重复读、幻读。

示例(不可重复读)

-- 事务A(RC级别)STARTTRANSACTION;SELECTbalanceFROMaccountWHEREid=1;-- 100-- 此时事务B修改并提交-- 事务A再次查询SELECTbalanceFROMaccountWHEREid=1;-- 可能变成90,不可重复读

InnoDB 实现:通过MVCC(多版本并发控制),每次SELECT都会重新生成一个一致性视图(read view),所以能看到已提交的最新数据,因此不可重复读无法避免。

3. 可重复读(Repeatable Read,RR)

MySQL InnoDB 的默认级别。保证在同一事务内,多次读取同一条记录的结果一致。

解决的问题:脏读、不可重复读。
幻读问题:理论上幻读仍可能发生,但 InnoDB 通过间隙锁(Gap Lock)MVCC在大多数场景下避免了幻读。

示例(可重复读实现)

-- 事务A(RR级别)STARTTRANSACTION;SELECTbalanceFROMaccountWHEREid=1;-- 100-- 事务B修改并提交UPDATEaccountSETbalance=90WHEREid=1;COMMIT;-- 事务A再次查询SELECTbalanceFROMaccountWHEREid=1;-- 仍然是100(可重复读)

InnoDB 实现原理

  • 事务第一次SELECT时,生成一个read view(包含未提交事务的 ID 列表)。
  • 后续SELECT使用同一个 read view,因此看不到其他事务提交的修改,实现了可重复读。

幻读的解决
对于快照读(普通SELECT),MVCC 天然避免了幻读(因为快照不变)。
对于当前读(SELECT ... FOR UPDATE/UPDATE/DELETE),InnoDB 使用间隙锁锁住查询范围内的间隙,防止其他事务插入新行。

-- 事务ASELECT*FROMtWHEREid>10FORUPDATE;-- 事务B想插入 id=11,会被间隙锁阻塞,从而避免幻读

4. 串行化(Serializable)

所有事务串行执行,读操作也会加共享锁(读锁),写操作加排他锁。性能极低,几乎只在数据一致性要求极高且并发极低的场景使用。

解决的问题:脏读、不可重复读、幻读全部解决。
缺点:并发能力几乎为零,容易出现锁超时和死锁。


五、MVCC 机制浅析

MVCC(Multi-Version Concurrency Control)是 InnoDB 实现高并发隔离级别的核心。

原理

  • 每行记录隐藏两个字段:DB_TRX_ID(最后修改该行的事务ID)、DB_ROLL_PTR(指向 undo log 中旧版本记录的指针)。
  • 事务开始时,生成一个read view,包含当前活跃事务的 ID 列表(未提交的)。
  • 读取数据时,根据 read view 判断哪个版本可见:只看到事务 ID 小于当前事务且不在活跃列表中的版本。

不同隔离级别下的 read view 生成时机

  • RC:每条SELECT语句都生成一个新的 read view,所以能看到已提交的最新数据。
  • RR:第一条SELECT生成 read view,整个事务共用同一个,因此可重复读。

六、间隙锁(Gap Lock)与幻读

在 RR 级别下,InnoDB 通过间隙锁来防止幻读。

间隙锁:锁住索引记录之间的“间隙”,防止其他事务在间隙中插入新记录。

示例

-- 表 t 有 id 列(主键),已有记录 id=10, 20-- 事务ASELECT*FROMtWHEREidBETWEEN10AND20FORUPDATE;-- 此时 InnoDB 不仅锁住 id=10 和 20 的记录,还锁住 (10,20) 之间的间隙-- 事务B 想插入 id=15,会被阻塞,直到事务A提交

注意

  • 如果查询使用唯一索引等值查询且记录存在,则只锁记录,不锁间隙(不会产生幻读风险)。
  • 如果查询使用普通索引或范围查询,会锁间隙。
  • 串行化级别也会加间隙锁,但还会对读操作加共享锁。

七、如何查看和设置隔离级别?

-- 查看当前会话隔离级别SELECT@@transaction_isolation;-- MySQL 8.0SELECT@@tx_isolation;-- MySQL 5.7-- 查看全局隔离级别SELECT@@global.transaction_isolation;-- 设置会话隔离级别SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED;-- 设置全局隔离级别(需要重启或重新连接生效)SETGLOBALTRANSACTIONISOLATIONLEVELREPEATABLEREAD;

八、常见面试追问

Q1:MySQL 默认隔离级别是 RR,为什么很多互联网公司改成 RC?

  • RC 的锁机制更简单(没有间隙锁),并发性能更高,尤其在主从复制中,RC + binlog_format=ROW 可以避免间隙锁带来的死锁问题。
  • RC 允许不可重复读,但在大多数业务场景(如订单、支付)中,单条查询通常不需要可重复读,通过业务逻辑或乐观锁即可保证一致性。
  • 但要注意:RC 下可能出现不可重复读和幻读,需要业务层面接受或使用其他手段。

Q2:RR 级别下能完全避免幻读吗?

普通快照读(不加锁的SELECT)不会出现幻读,因为快照不变。
当前读SELECT ... FOR UPDATE/UPDATE/DELETE)通过间隙锁避免了幻读。
但有一种情况例外:事务中先快照读,再当前读,可能会看到不同的行数(因为当前读会读取最新提交的数据)。但这不属于严格意义上的幻读(幻读定义是同一查询两次结果集不同,但这里查询方式不同)。一般面试中认为 RR 级别下 InnoDB 解决了幻读。

Q3:MVCC 和锁的关系?

  • MVCC 用于快照读(普通SELECT),实现无锁并发。
  • 锁(行锁、间隙锁)用于当前读(SELECT ... FOR UPDATEUPDATEDELETE),保证写操作的正确性。

Q4:如何模拟幻读?

在 RC 级别下,很容易模拟幻读:

-- 事务A(RC)STARTTRANSACTION;SELECT*FROMtWHEREid>10;-- 假设返回2行-- 事务B插入 id=11 并提交INSERTINTOtVALUES(11);COMMIT;-- 事务A再次查询SELECT*FROMtWHEREid>10;-- 返回3行,幻读

在 RR 级别下,如果使用当前读,间隙锁会阻塞插入,模拟需要一些技巧(如先快照读,再当前读)。

Q5:隔离级别对性能的影响?

  • 读未提交:无锁,性能最高但数据不可靠。
  • 读已提交:锁范围小,性能较好。
  • 可重复读:加间隙锁,可能导致更多锁冲突,性能中等。
  • 串行化:大量锁等待,性能最差。

九、总结

隔离级别脏读不可重复读幻读实现方式(InnoDB)
读未提交可能可能可能无锁,直接读最新版本
读已提交不可能可能可能每条SELECT生成read view
可重复读不可能不可能通常不可能(间隙锁)第一条SELECT生成read view,当前读加间隙锁
串行化不可能不可能不可能读加共享锁,写加排他锁,事务串行

一句话记住隔离级别读未提交脏数据,读已提交不脏但幻不可重;可重复读默认防幻,串行化性能最差

理解事务隔离级别是数据库优化和面试的基础。希望这篇文章能帮你彻底掌握 MySQL 事务隔离的核心知识,欢迎继续讨论。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 10:53:46

NVDLA软件栈全解析:从Caffe模型到嵌入式设备推理的完整流程

NVDLA软件栈全解析:从Caffe模型到嵌入式设备推理的完整流程 在边缘计算和物联网设备中部署深度学习模型时,性能和效率往往成为关键瓶颈。NVDLA(NVIDIA深度学习加速器)作为开源硬件架构,提供了一套完整的软件工具链&…

作者头像 李华
网站建设 2026/4/22 10:52:18

终极指南:Noto字体如何为800+语言提供完美多语言支持

终极指南:Noto字体如何为800语言提供完美多语言支持 【免费下载链接】noto-fonts Noto fonts, except for CJK and emoji 项目地址: https://gitcode.com/gh_mirrors/no/noto-fonts Noto字体是Google开发的开源字体家族,旨在消除"豆腐块&qu…

作者头像 李华
网站建设 2026/4/22 10:51:53

别再只盯着速率了!工业相机选型,CameraLink、CXP、GigE、USB协议背后的成本与实战考量

工业相机协议选型实战:从速率神话到系统成本的全维度决策 当生产线上的视觉检测系统因图像传输延迟导致良品率下降15%时,工程师们才意识到——协议选型的失误正在吞噬企业利润。工业相机接口协议的选择远非简单的速率对比,而是一场涉及硬件成…

作者头像 李华
网站建设 2026/4/22 10:47:10

CANopen设备现场配置避坑指南:LSS协议详解与节点ID/波特率设置全流程

CANopen设备现场配置避坑指南:LSS协议详解与节点ID/波特率设置全流程 当你面对一批出厂节点ID相同或未知的CANopen从站设备时,如何高效、安全地完成节点ID和网络波特率的配置?这可能是每个现场工程师都曾头疼的问题。本文将深入解析LSS&#…

作者头像 李华