MySQL事务从ACID到分布式事务:底层原理与实战排查全解析 你打开这篇文章大概率是因为“MySQL事务”这四个字在面试里被问到过或者在线上排查数据不一致问题时被折磨过。作为后端开发事务这块属于那种“看着都会一问就懵一实战就出问题”的知识点。我当年面大厂的时候从ACID问到隔离级别再从MVCC问到分布式事务一个问题接着一个问题步步紧逼那时候我才意识到光背住“原子性、一致性、隔离性、持久性”这四个词是远远不够的。这篇文章我就把MySQL事务这条线完整梳理一遍不是给你念概念而是把每个知识点背后的设计逻辑和实际应用场景讲清楚。无论你是准备面试还是想解决实际开发中的事务问题或者单纯想把MySQL的底层机制搞明白这篇文章都适合你。我会从基础特性讲到隔离级别再深入MVCC和日志机制最后聊聊Spring事务和分布式事务这些面试重灾区。全文偏实战向能落地的那种。1. 事务四大特性ACID面试第一问的完整拆解ACID是事务的基石面试官问“事务的特性是什么”答案就是原子性、一致性、隔离性、持久性。但如果你只背这四个词面试官下一句就会让你“分别解释一下最好结合底层实现”。所以我们要把这四个特性吃透知道它们各自的含义也知道MySQL底层靠什么机制来保证。1.1 原子性要么全成功要么全回滚原子性Atomicity说的是一个事务里的所有操作是一个不可分割的整体。比如转账场景A账户扣100B账户加100这两个操作要么都成功要么都失败。如果扣款成功但加款失败事务就回滚到初始状态最终结果就像什么都没发生一样。MySQL靠什么实现原子性答案是undo log回滚日志。事务执行过程中每次修改数据之前InnoDB会先把修改前的数据记录到undo log里。如果事务执行到一半需要回滚InnoDB就根据undo log里的记录把数据恢复到修改前的状态。你可以把undo log理解成草稿纸写错了可以擦掉重来。需要特别注意的是undo log不只是存一行旧值那么简单它记录的是完整的反向操作UPDATE语句会记录一条对应的UPDATE来还原旧值DELETE语句会记录一条INSERT来还原数据。1.2 一致性最抽象也最核心的特性一致性Consistency指的是事务执行前后数据库的完整性约束没有被破坏。这里有个常见的误解很多人以为一致性是数据库自己保证的实际上一致性是一个上层概念它是原子性、隔离性和持久性三者合力作用的结果。数据库的约束主键、外键、唯一键、非空等保证了静态一致性而事务机制保证了动态一致性。举个例子转账业务要求“A扣的钱等于B加的钱”这个业务规则数据库本身并不知道它需要应用层在代码里保证再加上事务的原子性才能确保无论系统发生什么意外账户总额不变。所以面试时你可以这样回答一致性是事务的最终目标原子性、隔离性、持久性是手段数据库约束是底线业务约束靠应用层实现。1.3 隔离性并发事务互不干扰的保证隔离性Isolation关注的是多个事务并发执行时彼此之间不应该互相干扰。如果两个事务同时修改同一行数据或者一个事务读取另一个事务还没提交的修改就会产生各种并发问题。MySQL通过锁和MVCC多版本并发控制来保证隔离性。关于MVCC后面会有专门章节详细展开这里先建立一个认知隔离性并不是完全串行化那样性能太差而是在“数据正确”和“并发性能”之间做一个平衡。不同的隔离级别就是不同的平衡策略。1.4 持久性数据一旦提交就不丢失持久性Durability指一个事务提交后它对数据的修改就应该永久保存下来即使系统崩溃、断电数据也不能丢。MySQL靠redo log重做日志来实现持久性。这里有个非常重要的机制叫WALWrite-Ahead Logging预写日志。当你提交一个事务时InnoDB不会立刻把修改后的数据刷到磁盘的数据文件里那样太慢了而是先把变更记录写入redo log一旦redo log落盘事务就算提交成功。即使数据页还没刷盘系统崩溃了重启后MySQL也会根据redo log把数据恢复出来。redo log是物理日志记录的是“对某个数据页做了什么修改”属于InnoDB引擎层面的日志这一点面试时经常被追问需要和binlog区分开后面我会详细对比。2. 隔离级别深度解析脏读、不可重复读、幻读都是什么隔离级别是MySQL事务面试中出场率最高的考点重点在于不仅要说出四种级别还要能解释清楚每种级别解决了什么问题以及产生什么问题。2.1 三种并发一致性问题在说隔离级别之前需要先建立对三种并发问题的直观理解。脏读Dirty Read事务A读取了事务B尚未提交的数据然后事务B回滚了那么事务A读到的就是“不存在”的数据这就是脏读。实际上脏数据在这里并不带贬义它指的是未提交的临时数据。举个例子事务A给商品价格从100改成80还没提交事务B查询价格发现是80觉得很便宜就下单了结果事务A回滚了价格还是100事务B就冤大头了。不可重复读Non-Repeatable Read事务A内两次读取同一行数据结果发现两次数据不一样。原因是事务A第一次读取后事务B提交了对这行数据的修改事务A第二次读取就看到了新值。核心特征是同一行数据内容发生了变化强调的是修改操作UPDATE。幻读Phantom Read事务A内两次执行同一个查询结果发现第二次多了一堆之前不存在的行。原因是事务B插入了新的数据INSERT导致事务A两次查询的结果集不一致。核心特征是数据行数发生了变化强调的是插入或删除操作。打个比方你第一次数羊圈里有10只羊数完回头吃口草再数一遍发现变成了12只多了两只就跟幻觉一样。2.2 四种隔离级别分别解决了什么MySQL标准定义了四种隔离级别从低到高分别是读未提交READ UNCOMMITTED最低的隔离级别允许读取尚未提交的数据变更所有三种并发问题都存在性能最好但几乎没人用。读已提交READ COMMITTED只有事务提交后才能被其他事务读到解决了脏读但不可重复读和幻读依然可能发生。这是Oracle、PostgreSQL等数据库的默认级别。可重复读REPEATABLE READ事务启动后多次读取同一范围的数据集时结果保持一致解决了脏读和不可重复读但幻读理论上还存在。这是InnoDB的默认隔离级别。串行化SERIALIZABLE最高隔离级别所有事务串行执行解决了所有并发问题但性能极低相当于给所有读写操作都加了锁。这里需要特别强调一个MySQL的特殊之处InnoDB在可重复读级别下通过MVCC加间隙锁Gap Lock的机制已经几乎完全规避了幻读问题只是在某些特定场景下比如当前读与插入操作并发仍可能出现幻读。所以严格来说MySQL的默认级别提供的隔离能力比其他数据库更强。2.3 为什么面试官总爱问“MySQL为什么默认用可重复读”这个问题是面试加分项。很多资料说这是因为MySQL早期binlog只有STATEMENT格式无法支持读已提交级别下的主从一致性。这里不展开太多历史细节说个实用的理解方向binlog记录了SQL执行的逻辑操作用于主从复制。如果隔离级别是读已提交事务A先UPDATE了一条记录然后事务B又UPDATE了同一条记录并且事务B先提交binlog记录的先后顺序可能与实际执行顺序不一致。在无锁或某些并发场景下从库回放binlog时可能导致最终数据不一致。可重复读级别结合间隙锁使得同一事务内的查询使用一致的快照降低了这种不一致的风险。理解到这个层面面试基本就能加分了。3. MVCC机制可重复读的底层实现原理MVCC全称是Multi-Version Concurrency Control多版本并发控制。这是MySQL事务面试的高阶内容也是区分“背过八股”和“真正理解”的分水岭。3.1 两个隐藏字段和版本链InnoDB的行数据中除了业务字段还隐藏着几个关键字段DB_TRX_ID最近修改该行数据的事务ID每次事务对某行数据进行修改时都会把这个事务的ID记录在这里。DB_ROLL_PTR回滚指针指向该行数据上一个版本在undo log中的位置。通过这个指针可以将多个版本的数据串联成一条版本链。你可能听说过还有一个DB_ROW_ID但它是没有主键时InnoDB自动生成的隐藏主键跟MVCC关系不大不展开。当多个事务依次修改同一行数据时每修改一次就会生成一个版本写入undo log并通过回滚指针串成链表。版本链的头部就是最新版本的数据越往链尾走就是越旧的版本。3.2 ReadView的生成规则ReadView一致性视图是MVCC的核心机制之一用来判断当前事务能看见版本链上的哪些版本。ReadView中包含几个关键属性trx_ids生成ReadView时当前活跃未提交的所有事务ID列表。min_trx_idtrx_ids中最小的那个事务ID。max_trx_id生成ReadView时InnoDB将要分配给下一个事务的ID等于当前最大事务ID加1。creator_trx_id创建这个ReadView的事务自己的ID。当某个事务要读取一行数据时会拿着版本链上每个版本的DB_TRX_ID去和ReadView做比较判断规则如下如果DB_TRX_ID小于min_trx_id说明这个版本的修改事务在ReadView生成之前就已经提交了该版本可见。如果DB_TRX_ID大于等于max_trx_id说明这个版本的修改事务是在ReadView生成之后才开始的不可见。如果DB_TRX_ID在min_trx_id和max_trx_id之间需要进一步判断这个事务ID是否在trx_ids活跃列表里。如果在说明修改事务还没提交该版本不可见如果不在说明修改事务已经提交该版本可见。如果是修改事务就是当前事务自己那一定可见。这套规则解决了读已提交和可重复读的核心区别读已提交级别下每次SELECT都会生成一个新的ReadView可重复读级别下只在事务第一次SELECT时生成ReadView后续所有SELECT复用同一个ReadView。这就是为什么可重复读级别下事务内多次查询结果一致因为同一个事务始终看到的是同一个快照。3.3 快照读与当前读MVCC解决的是快照读普通SELECT的并发问题但有一类操作不走快照读走的是当前读。当前读读取的是数据的最新版本并且会对读取的记录加锁。常见当前读场景包括SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE、UPDATE、DELETE、INSERT。为什么这些操作要当前读因为你要修改数据必须基于最新版本去改用旧快照去改会造成数据覆盖。理解快照读和当前读的区别非常重要否则你会遇到一个经典的“坑”在可重复读级别下事务A先快照读查到一条记录然后事务B插入了一条新记录并提交事务A再执行UPDATE会发现把事务B新插入的记录也一起更新了。这是因为UPDATE是当前读它能看到最新的数据并不会被快照挡住。很多人以为可重复读就什么都读不到新数据其实是不准确的。3.4 可重复读下依然可能出现的幻读场景既然MySQL的MVCC和间隙锁已经很强大为什么说幻读“几乎所有场景都规避了但某些特殊场景仍然存在”我给你说一个典型场景事务A先执行快照读查出一个符合条件的记录集合没有开启任何锁。事务B插入一条新记录并提交这条新记录符合事务A的查询条件。事务A再次执行同样的查询因为复用了旧ReadView快照读依然查不到新记录看起来没有幻读。但是如果事务A在事务B提交后执行UPDATE或者SELECT ... FOR UPDATE这类当前读就可能会读到或者更新到事务B插入的新记录。这个结果跟事务A第一次查询的结果集不一致就属于幻读的变种。这也是面试官常用来追问的点你能答出这个场景说明不是死记硬背。4. 事务日志三兄弟undo log、redo log、binlog的分工合作事务的可靠性和一致性离不开日志体系面试到了这个环节通常都是需要画图解释的。虽然我不会用流程图但我会用直白的方式给你说清楚每个日志的定位和它们之间的关系。4.1 undo log回滚与MVCC的地基前面说原子性时已经提到了undo log它的作用不仅是回滚事务还承担了MVCC版本链的存储职责。当数据被修改时旧版本会写入undo log多个版本的记录通过回滚指针串联起来。事务回滚时InnoDB根据undo log执行反向操作把数据恢复到事务开始前的状态。需要注意的是undo log不只在事务回滚时发挥作用在事务提交后也不一定会立即删除。如果还有其他事务的ReadView引用了旧版本数据undo log就需要保留直到没有任何事务需要看到这些旧版本为止。所以长事务会导致undo log膨胀占用大量磁盘空间这也是实际开发中要控制事务大小的原因之一。4.2 redo log崩溃恢复的保命符redo log是InnoDB存储引擎特有的物理日志记录的是“数据页的物理修改操作”比如“在第X号数据页的第Y偏移量写入值Z”。它采用循环写入的方式大小固定写满后会覆盖旧的日志记录。为什么需要redo log原因就是WAL机制。如果每次提交事务都同步把修改后的数据页刷新到磁盘随机IO性能会非常差。有了redo log事务提交时只需要把日志顺序写入磁盘顺序IO比随机IO快一到两个数量级性能提升非常明显。如果数据库崩溃重启时InnoDB会扫描redo log把尚未刷新到数据页的修改重新应用一遍确保已提交事务的数据不丢失。4.3 binlog数据库层面的逻辑日志binlog是MySQL Server层生成的逻辑日志它记录的是SQL语句的原始逻辑或行变更的影像主要用于主从复制和数据恢复。关键区别在于redo log是InnoDB引擎负责记录的物理日志而binlog是MySQL Server层负责记录的逻辑日志两者产生的时机和用途都不一样。一个事务在提交时内部会经历一个“两阶段提交”的过程事务执行阶段修改数据并写入undo log和redo log状态为prepare。提交阶段先写入binlog然后把redo log的状态改为commit。这个设计的核心目的就是保证redo log和binlog的一致性。如果binlog写成功但redo log没提交从库执行了binlog主库没对应的数据变更主从数据就会不一致。如果反过来redo log提交了但binlog没写成功主库有数据变更从库没有也会不一致。两阶段提交保证了这两个日志要么同时成功要么同时回滚这是主从复制一致性的基础保障。4.4 一次事务提交的完整流程为了把日志机制串起来我描述一条UPDATE语句的完整执行链路事务执行UPDATE语句InnoDB先从存储引擎中读取目标数据页如果数据页不在缓冲池Buffer Pool中先从磁盘加载到缓冲池。对目标行加锁然后把修改前的数据旧版本写入undo log。修改缓冲池中的数据页同时生成redo log记录状态为prepare。事务提交时将binlog写入磁盘并刷盘。redo log状态更新为commit完成两阶段提交。后台线程在合适时机把缓冲池中脏页刷到磁盘由innodb_io_capacity等参数控制刷盘速度。理解了这条主链路你就能回答“为什么事务提交后数据不会丢”“为什么主从数据能保持一致”这两个问题。5. Spring事务传播机制与失效场景面试重灾区作为一个后端开发MySQL事务必须和Spring事务结合起来学习。因为我们在实际开发中很少直接操作数据库事务绝大多数情况是使用Spring的Transactional注解。面试时这一块的常见问题是“Spring事务的传播行为有哪些”和“事务失效的场景有哪些”。5.1 七种事务传播机制速查Spring定义的事务传播行为解决的是“一个事务方法调用另一个事务方法时两个方法的事务如何合并”的问题。面试中最常被问到的三个是REQUIRED默认如果当前存在事务就加入当前事务如果当前没有事务就新建一个事务。这是最常用的传播行为适合大多数业务场景。REQUIRES_NEW无论如何都新建一个事务。如果当前存在事务就把当前事务挂起。适合需要独立事务的场景比如记录操作日志日志事务失败不应该影响主业务事务。NESTED嵌套事务如果当前存在事务则创建一个保存点Savepoint内层方法执行失败后可以仅回滚到保存点外层事务可以选择继续提交或自行回滚。这是MySQL 5.7及以上版本配合Spring才能支持的机制。另外四种分别是SUPPORTS有事务就加入没有就以非事务方式执行、NOT_SUPPORTED以非事务方式执行有事务就先挂起、MANDATORY必须有事务否则抛异常、NEVER必须没有事务否则抛异常。这四种在实际业务中很少使用面试时说出名称和核心含义即可。5.2 事务失效的经典场景和原因事务失效是线上问题重灾区也是面试必备考点。我见过的失效场景可以归结为以下几类同一个类内部方法调用导致失效。这是最常见的一坑。类A的方法x调用了方法yy上有Transactional注解但x没有。因为Spring事务是基于AOP代理的只有通过代理对象调用方法时事务注解才会生效。内部调用走的是this指针调用绕过了代理事务自然不生效。权限修饰符问题。Spring事务默认只对public方法生效因为代理机制的限制private、protected、default方法上的事务注解都不会被解析。异常被捕获吞掉。方法内部try-catch捕获了异常而没有重新抛出事务管理器感知不到异常就不会回滚。正确做法是如果方法内部需要捕获异常应该将异常重新抛出或者使用编程式事务手动回滚。抛出异常类型不对。Spring默认只对RuntimeException和Error回滚对受检异常Checked Exception不回滚。如果你抛出一个IOException等受检异常事务管理器不会触发回滚。这是非常隐蔽的坑解决方案是使用Transactional(rollbackFor Exception.class)来扩大回滚范围。方法中的操作不是走数据库事务。比如事务方法里调用了一个异步线程去执行数据库操作主线程事务提交了异步线程的操作是独立的事务两者不在同一个事务上下文里无法保证一致性。数据库表引擎不支持事务。MySQL中只有InnoDB支持事务如果表是MyISAM引擎Transactional注解再完美也无济于事。5.3 自调用失效问题的两种解法针对最典型的自调用问题有两种主流解法把需要事务的方法拆到另一个Service类中由外层类注入该Service并调用这样就能通过Spring代理访问。在自己的类中注入自己或者通过ApplicationContext获取代理对象来调用。比如Service public class OrderService { Autowired private ApplicationContext applicationContext; public void outerMethod() { OrderService proxy applicationContext.getBean(OrderService.class); proxy.innerMethod(); } Transactional public void innerMethod() { // 事务逻辑 } }这种方式虽然能用但不太优雅我更推荐第一种方案把事务方法放进独立的Service中职责清晰也更符合单一职责原则。6. 分布式事务从本地事务到全局一致性聊完单机事务面试官通常会顺势把话题延伸到分布式事务。因为互联网业务一旦拆分成微服务一个业务操作会跨多个服务、多个数据库本地事务就管不住了。这块内容多而杂我挑最核心的知识点和方案讲。6.1 为什么要分布式事务以及XA方案单体架构下一个事务操作多个表靠数据库本地事务就能搞定。但微服务架构下一个操作可能同时调用订单服务、库存服务、账户服务每个服务操作各自的数据库无法用同一个数据库事务来保证所有操作同时成功或失败这就是分布式事务要解决的问题。最经典的方案是XA协议也就是两阶段提交2PC。XA协议把事务提交分成两个阶段准备阶段Prepare事务协调者向所有参与者发送准备请求参与者执行事务但不提交返回“可以提交”或“必须回滚”。提交阶段Commit/Rollback协调者如果收到所有参与者“可以提交”的响应就发送提交指令只要有一个参与者返回“必须回滚”就发送回滚指令。XA的优点是强一致性但缺点也很明显准备阶段会占用资源锁定性能较差而且协调者存在单点问题如果协调者在第二阶段宕机整个分布式事务就卡住了。所以XA实际应用并不多我在实际项目中几乎没有用到过。6.2 TCC与本地消息表TCCTry-Confirm-Cancel是目前分布式事务方案中偏业务层面的实现。它把每个事务操作拆成三个阶段Try阶段完成业务检查并预留必要资源比如冻结库存。Confirm阶段确认执行业务操作真正扣减库存。Cancel阶段取消执行释放Try阶段预留的资源。TCC的优点是业务侵入性强但灵活适合对一致性要求较高的场景。缺点是实现复杂度非常高每个业务都要写Try、Confirm、Cancel三套逻辑。实际项目中TCC通常借助框架如Seata来实现单独手写TCC成本太高。本地消息表是另一种常见的最终一致性方案。核心思路是在业务操作所在数据库中建立一张消息表。业务操作和写消息表在同一个本地事务里完成。然后通过定时任务扫描消息表把未发送的消息投递到消息队列下游服务消费消息后执行自己的业务操作再回调确认。如果下游处理失败可以重试。它的实现简单可靠但需要消息表跟业务表在同一个库里对数据库有额外压力。6.3 最大努力通知与订单库存场景最大努力通知是互联网业务中经常提及的方案核心特点是不保证消息一定送达但会尽最大努力并且不保证最终一致性而是通过不断重试来最大可能实现一致性。适合用于对实时性要求不高、允许短暂不一致的业务场景比如支付结果通知、积分发放等。说到订单与库存的分布式事务这是最经典的案例。用户下单时订单服务写入订单库存服务扣减库存账户服务扣减金额。如果三个服务各自独立提交事务任何一个失败都会造成数据不一致。目前工业界比较主流的做法是订单服务和库存服务不强制要求强一致性而是通过消息队列最终一致。用户下单后订单服务先写入本地订单表和消息表本地事务然后异步发送“扣减库存”消息库存服务消费消息扣减库存。如果库存不足库存服务反向发送“订单取消”消息。这套方案把强一致降级为最终一致吞吐量和可用性都有保障。6.4 Seata分布式事务核心原理Seata是当前Java生态中最常用的分布式事务框架它支持的AT模式Auto Transaction很实用。AT模式的核心思想是业务代码零侵入框架自动完成回滚数据的记录和补偿。具体来说Seata AT模式会拦截业务SQL在执行业务SQL之前先查询原始数据快照保存到undo_log表中然后执行业务SQL并在全局事务提交前把修改后的数据快照也保存起来。如果全局事务需要回滚Seata根据undo_log中的前后快照自动生成反向SQL来恢复数据。业务层完全不用感知这些细节框架层面就搞定了。Seata还支持TCC模式、Saga模式和XA模式不同模式有不同适用场景。实际项目中AT模式因为侵入性低是最受欢迎的选择。7. 面试高频追问与实战排查经验最后这部分我整理了一些面试和实战中高频遇到的问题和排查技巧直接拿来就能用。7.1 面试常问快问快答以下是我这些年面试别人和被面试时遇到的高频问题的速答版本MySQL默认隔离级别是什么InnoDB的可重复读REPEATABLE READ。可重复读如何实现通过MVCC事务第一次SELECT时生成ReadView后续复用保证快照一致性。MySQL如何避免幻读快照读靠MVCC当前读靠间隙锁Gap Lock和临键锁Next-Key Lock。间隙锁是什么锁定一个范围但不包含记录本身范围是索引值的开区间。它为了防止其他事务在范围内插入新数据从而避免幻读。什么时候间隙锁会失效如果查询条件没有走索引InnoDB会对全表所有记录加锁相当于锁全表这对性能影响很大。长事务有什么危害占用连接资源导致undo log无法及时清理锁持有时间长阻塞其他事务还可能影响主从同步延迟。如何查看当前数据库事务状态SELECT * FROM information_schema.INNODB_TRX;这条SQL可以看到当前所有正在执行的事务包括事务ID、状态、锁等待时间、执行的SQL等是排查问题的第一工具。如何查看锁等待情况SELECT * FROM sys.innodb_lock_waits;通过这张视图可以看到哪个事务在等哪个锁是谁阻塞了谁。再配合下面这条SQL找到阻塞源SELECT * FROM performance_schema.data_lock_waits;7.2 线上环境排查事务问题的方法实际工作中事务问题最常见的表现是“接口卡死”或“CPU飙升”背后往往是锁等待或者长事务。排查步骤我建议这样来做第一步先看是否有锁等待。用前面说的INNODB_TRX和innodb_lock_waits视图查到等待中的事务和执行中的事务分析谁锁了谁。如果是慢SQL长期占用锁那核心是优化SQL索引如果是事务没提交导致锁不释放那就是代码问题。第二步检查是否有事务长时间不提交。通过INNODB_TRX看到trx_state不为RUNNING的事务或者trx_started时间很久远的事务基本就是代码里有未关闭的事务或者异常路径没回滚。第三步确认是不是大事务。一个事务内操作行数太多或者一次事务里做了远程调用RPC、HTTP都会导致事务时间过长。我见过一个生产事故事务方法里调用第三方接口第三方响应超时10秒导致数据库连接被占用10秒数据库连接池被打满应用全线不可用。这种问题的核心原则是事务内不要做远程调用远程调用必须放到事务外。7.3 设置合适的事务隔离级别最后聊聊隔离级别的选择。MySQL默认可重复读很多团队直接用默认值但需要知道如果业务允许读已提交在某些场景下性能更好因为可重复读需要额外的间隙锁来避免幻读而读已提交不需要间隙锁并发写入性能更高。一个可复用的经验如果业务并发写同一张表很频繁且数据一致性可以在应用层控制可以考虑把隔离级别设置为READ COMMITTED如果业务要求同一事务内多次查询结果必须一致比如报表统计类场景保留REPEATABLE READ更安全。修改方法可以在MySQL配置文件的mysqld段中设置transaction-isolation READ-COMMITTED或者在会话级动态设置SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;但在生产环境修改隔离级别之前一定要充分评估对已有业务的影响特别是主从复制和数据一致性方面。我个人的建议是默认保持可重复读除非你能明确说清楚改级别解决什么问题否则不要为了“性能提升”盲目修改。写在最后的一点经验跟事务打交道这几年踩过最大的坑总结起来就一句话八股文背得再熟不如亲手写一个事务失效的Demo。建议你把文章里提到的自调用失效、异常被吞失效、传播机制REQUIRES_NEW和NESTED的区别都自己写代码验证一遍。只有你亲眼看到“为什么不生效”和“为什么回滚了但没完全回滚”你对事务的理解才算真正落地。面试官问到事务时你能从机制讲到场景再到排查经验他会觉得你是一个有实战底蕴的人而不只是背了一份答案。