当前位置: 首页 > news >正文

MySQL事务隔离级别详解

MySQL 事务隔离级别详解

记得刚入职的时候,线上出了个诡异的 bug:用户 A 给自己账户充值了 100 块,结果余额没变。查了半天,发现是事务隔离级别没搞懂,导致一个事务读到了另一个事务未提交的数据。

今天咱们就来彻底搞懂 MySQL 的事务隔离级别,看完这篇,你就能避开 90% 的事务坑。

事务的 ACID 是啥?

先铺垫一下,事务有四大特性,记住这个面试必问:

  • A(Atomicity)原子性:事务要么全做,要么全不做,不可能只执行一半
  • C(Consistency)一致性:事务执行前后,数据库都得是"合法"的状态
  • I(Isolation)隔离性:多个事务并发执行,彼此之间不能互相干扰
  • D(Durability)持久性:事务提交后,数据就得永久保存,哪怕数据库宕机

今天重点是I(隔离性),也就是隔离级别。

没有隔离级别会出啥问题?

如果多个事务完全不隔离,会有三种经典问题:

1. 脏读(Dirty Read)

事务 A 读到了事务 B未提交的数据。如果事务 B 后面回滚了,那事务 A 读到的就是"脏数据"。

时间轴: T1: 事务A开始 T2: 事务B开始 T3: 事务B把小明余额从100改成200(未提交) T4: 事务A读取小明余额,读到200 ← 脏读! T5: 事务B回滚,小明余额变回100 T6: 事务A拿着200去干活,实际上余额只有100

2. 不可重复读(Non-Repeatable Read)

同一个事务内,两次读取同一行数据,结果不一样(因为别的事务修改并提交了这行数据)。

时间轴: T1: 事务A开始,读取小明余额=100 T2: 事务B开始,把小明余额改成200,提交 T3: 事务A再次读取小明余额=200 ← 不可重复读!

3. 幻读(Phantom Read)

同一个事务内,两次执行同样的查询,记录数量不一样(因为别的事务插入或删除了数据)。

时间轴: T1: 事务A开始,查询余额>100的用户,得到10条 T2: 事务B开始,插入一个余额150的新用户,提交 T3: 事务A再次查询余额>100的用户,得到11条 ← 幻读!

注意:不可重复读是针对已有记录的修改,幻读是针对记录数量的变化。

MySQL 的四种隔离级别

MySQL 定义了四种隔离级别,从低到高:

隔离级别脏读不可重复读幻读
READ UNCOMMITTED(读未提交)❌ 会❌ 会❌ 会
READ COMMITTED(读已提交)✅ 不会❌ 会❌ 会
REPEATABLE READ(可重复读)✅ 不会✅ 不会❌ 会(InnoDB 不会)
SERIALIZABLE(串行化)✅ 不会✅ 不会✅ 不会

MySQL 默认隔离级别是 REPEATABLE READ(可重复读),而且 InnoDB 通过 Next-Key Lock 解决了幻读问题,所以实际上 REPEATABLE READ 已经能防住所有问题了。

实战:看看每个隔离级别的表现

咱们用实际 SQL 来验证一下,假设你有个账户表:

CREATETABLEaccounts(idINTPRIMARYKEY,nameVARCHAR(50),balanceDECIMAL(10,2));INSERTINTOaccountsVALUES(1,'小明',100.00);

1. READ UNCOMMITTED(读未提交)

-- 会话ASETSESSIONTRANSACTIONISOLATIONLEVELREADUNCOMMITTED;STARTTRANSACTION;SELECTbalanceFROMaccountsWHEREid=1;-- 读到 100-- 会话B(另一个终端)STARTTRANSACTION;UPDATEaccountsSETbalance=200WHEREid=1;-- 注意:这里没提交!-- 会话ASELECTbalanceFROMaccountsWHEREid=1;-- 读到 200!脏读!

问题:会话 A 读到了会话 B 未提交的数据。如果会话 B 回滚,那会话 A 拿到的 200 就是无效的。

2. READ COMMITTED(读已提交)

-- 会话ASETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED;STARTTRANSACTION;SELECTbalanceFROMaccountsWHEREid=1;-- 读到 100-- 会话BSTARTTRANSACTION;UPDATEaccountsSETbalance=200WHEREid=1;-- 没提交-- 会话ASELECTbalanceFROMaccountsWHEREid=1;-- 还是读到 100(防脏读)-- 会话BCOMMIT;-- 会话ASELECTbalanceFROMaccountsWHEREid=1;-- 读到 200!不可重复读!

问题:同一个事务内,两次读取结果不一样(不可重复读)。

Oracle 默认就是这个级别,但 MySQL 默认不是。

3. REPEATABLE READ(可重复读,MySQL 默认)

-- 会话ASETSESSIONTRANSACTIONISOLATIONLEVELREPEATABLEREAD;STARTTRANSACTION;SELECTbalanceFROMaccountsWHEREid=1;-- 读到 100-- 会话BSTARTTRANSACTION;UPDATEaccountsSETbalance=200WHEREid=1;COMMIT;-- 会话ASELECTbalanceFROMaccountsWHEREid=1;-- 还是读到 100!可重复读!

牛逼之处:同一个事务内,多次读取同一行,结果一致。MySQL 是怎么做到的?MVCC(多版本并发控制)

简单说就是:每个事务开始时,会生成一个快照(Read View),后面的查询都基于这个快照,不受其他事务提交的影响。

那如果会话 A 想更新呢?

-- 会话AUPDATEaccountsSETbalance=balance+50WHEREid=1;-- 这里会加行锁,并且读取最新的值(200),所以结果是 250SELECTbalanceFROMaccountsWHEREid=1;-- 读到 250

注意:UPDATE/DELETE/INSERT 操作会读取最新数据(当前读),不受快照影响。

4. SERIALIZABLE(串行化)

-- 会话ASETSESSIONTRANSACTIONISOLATIONLEVELSERIALIZABLE;STARTTRANSACTION;SELECT*FROMaccountsWHEREid=1;-- 会话BSTARTTRANSACTION;INSERTINTOaccountsVALUES(2,'小红',300);-- 阻塞!等会话A提交才能执行

原理:SERIALIZABLE 会对所有读取加共享锁,写操作加排他锁,相当于事务排队执行,性能最差,但最安全。

一般不用,除非对数据一致性要求极高(比如金融场景)。

InnoDB 怎么解决幻读的?

前面我说了,InnoDB 的 REPEATABLE READ 能防幻读,这是怎么做到的?

答案是:Next-Key Lock(临键锁)

假设你的账户表有这些记录:

INSERTINTOaccountsVALUES(1,'小明',100),(5,'小红',200),(10,'小刚',300);

如果会话 A 执行:

-- 会话ASTARTTRANSACTION;SELECT*FROMaccountsWHEREid>5FORUPDATE;-- 命中了 id=10 这条记录

InnoDB 不会只锁id=10这一行,而是锁住(5, 10]这个区间(Next-Key Lock = 记录锁 + 间隙锁)。

那会话 B 想插入id=7的记录:

-- 会话BINSERTINTOaccountsVALUES(7,'小华',150);-- 阻塞!因为 (5, 10] 被锁住了

结果:会话 B 必须等会话 A 提交后才能插入,幻读就不可能发生了。

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

查看当前隔离级别

-- MySQL 8.0+SELECT@@transaction_isolation;-- 或者SHOWVARIABLESLIKE'transaction_isolation';-- MySQL 5.7 及之前SELECT@@tx_isolation;

设置隔离级别

-- 设置当前会话的隔离级别(只对当前连接有效)SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED;-- 设置全局隔离级别(对新连接有效,当前连接不变)SETGLOBALTRANSACTIONISOLATIONLEVELREADCOMMITTED;-- 修改配置文件(永久生效)-- 在 my.cnf 或 my.ini 里加:[mysqld]transaction-isolation=READ-COMMITTED

实战建议

1. 别随便改隔离级别

MySQL 默认 REPEATABLE READ 是经过大量实践验证的,能解决绝大多数问题。除非你有明确理由,否则别改。

我们公司就有人把隔离级别改成 READ COMMITTED,结果出了不可重复读的 bug,排查了一整天。

2. 长事务是性能杀手

隔离级别越高,事务之间的隔离性越好,但并发性能越差。如果你开了个长事务(比如跑了 10 分钟还没提交),会阻塞很多其他操作。

-- 查看当前运行的事务SELECT*FROMinformation_schema.innodb_trx;

建议:事务要尽量短小,用完马上提交。

3. 慎用 SELECT … FOR UPDATE

SELECT ... FOR UPDATE会加排他锁,容易引发死锁。

-- 会话ASTARTTRANSACTION;SELECT*FROMaccountsWHEREid=1FORUPDATE;-- 会话BSTARTTRANSACTION;SELECT*FROMaccountsWHEREid=2FORUPDATE;UPDATEaccountsSETbalance=100WHEREid=1;-- 阻塞-- 会话AUPDATEaccountsSETbalance=200WHEREid=2;-- 死锁!

建议:只在必要时用FOR UPDATE,并且尽量按固定顺序加锁(比如按 id 排序)。

总结

  • 事务隔离级别从低到高:READ UNCOMMITTED → READ COMMITTED → REPEATABLE READ → SERIALIZABLE
  • 隔离级别越低,并发性能越好,但数据一致性越差
  • MySQL 默认 REPEATABLE READ,通过 MVCC 防不可重复读,通过 Next-Key Lock 防幻读
  • 别随便改隔离级别,慎用长事务和SELECT ... FOR UPDATE

如果你能把 MVCC 和 Next-Key Lock 讲清楚,面试官绝对眼前一亮。


实战代码都在我本地跑过,你可以放心复制。如果有问题,欢迎评论区交流!

http://www.cnnetsun.cn/news/2525063.html

相关文章:

  • CMU localPlanner算法深度解析:从‘采样路径’到‘最优选择’的完整决策逻辑与代码实现
  • Source Han Serif CN:免费开源中文字体如何彻底改变你的中文排版体验
  • 告别串口调试烦恼:用MAX3221EUE+芯片搞定TTL转RS232的完整电路与PCB布局指南
  • 有哪些AI论文平台是真的契合专业内容,而不是随意编造?
  • Frida调试实战:frida-ps -U连接失败的5大根因与端口转发技巧
  • 如何5分钟制作专业学术演示文稿:上海交通大学LaTeX幻灯片模板终极指南
  • 终极指南:Windows 11 LTSC企业版快速安装微软商店完整方案
  • 深度解析Unlock-Music:浏览器端音乐解密技术实战指南
  • 别再傻傻分不清了!一文搞懂光敏、热敏、红外传感器模块的通用电路与核心区别
  • 3个步骤:如何在Windows 11上实现Android应用无缝安装与管理
  • 番茄小说下载器:跨平台小说下载终极解决方案
  • 内容创作者的“第二大脑”:AI如何重塑从灵感到发布的效率链?
  • Finch开源生态:插件、模板与社区资源全解析
  • LibreDWG:免费开源的DWG文件转换终极指南
  • 如何在Windows上进行高效屏幕标注?ppInk免费开源工具完全指南
  • 【办公小助手】OpenClaw 对接 DeepSeek 模型配置详细教程(包含安装包)
  • Flyd未来展望:响应式编程的终极发展趋势与社区路线图指南
  • 嵌入式音频拾音方案:PI‑36 双 MIC 降噪模块应用与设计
  • Transformer注意力机制深度解析:3大设计要点与最佳实践
  • 3倍速畅玩体验:HsMod炉石传说个性化改造方案
  • 彻底告别摇杆漂移:Joy-Con Toolkit让你的Switch手柄重获新生
  • RPFM终极指南:全面战争模组制作从未如此简单
  • 如何快速解锁通达信数据:Python金融分析的终极指南
  • MediaCrawler:构建企业级社交媒体数据采集系统的技术深度解析
  • OpenRocket火箭设计仿真:从零到专家的7步完整指南
  • SleeperX:macOS系统级电源管理框架的技术实现与应用
  • Open Spectrometer Python性能优化:提升光谱数据处理效率的7个技巧
  • Java 项目打包与部署完全指南:JAR vs WAR,从构建到运行
  • 革命性Excel MCP Server:无需安装Excel的终极数据处理解决方案
  • Cortex-R52调试ROM地址配置与ARMv8调试架构解析