事务的边界问题,如何判断数据回滚时机。 事务回滚到底发生在什么时候在日常开发中关于事务最常见的误解之一是只要后续代码报错前面已经写入数据库的数据就一定会回滚。这句话并不准确。事务是否回滚并不取决于“有没有异常”而取决于一个更关键的问题异常发生时事务是否还处于可回滚阶段。本文围绕这个核心问题展开重点说明以下几个场景提交前报错是否回滚提交时报错是否回滚提交后报错是否回滚多服务场景下为什么“已经提交”的数据有时仍然会被撤回一、先给出结论可以先记住下面这组结论事务提交前发生异常前面已经执行的写库操作通常会回滚。事务提交过程中失败整笔事务通常也会回滚。事务已经提交完成后再发生异常前面已经提交的数据通常不会自动回滚。如果系统外层还存在更大范围的事务控制那么某一步本地已提交的数据最终仍可能被整体撤回。因此判断事务是否回滚最重要的不是看“是否抛异常”而是看异常发生时事务究竟结束了没有。二、事务回滚的本质事务的核心目标是把一组操作当成一个整体来处理。这个整体在最终结果上只有两种状态要么全部成功要么全部不生效但这里有一个前提这组操作仍然处于事务控制范围之内。一旦事务已经真正提交完成数据库已经确认落库那么后面即使再抛出异常这个异常也已经不再属于“当前事务的回滚阶段”自然也无法把已经提交的数据自动撤销。三、用一张图看懂事务回滚时机提交前提交时提交后开始执行业务执行写库操作异常发生在什么时候事务尚未结束前面写入的数据通常会一起回滚提交未真正成功整笔事务通常仍会回滚事务已经完成后续异常只表示后续动作失败前面已提交的数据通常不会自动回滚这张图表达的是事务判断中最关键的一条主线只有在事务尚未真正结束时异常才有机会触发整体回滚。四、三种最典型的事务场景1. 提交前报错这是最常见的事务回滚场景。假设一个方法中包含两步数据库操作更新订单状态插入一条字典数据如果第二步执行时报错而整个事务尚未提交那么通常结果是订单状态更新回滚字典插入回滚也就是说这两步都不会最终生效。这也是大多数开发者最熟悉的事务行为。2. 提交过程中失败有些场景下代码本身已经执行到尾部但数据库在真正提交时失败了。例如提交阶段发生数据库异常提交时被事务框架判定为失败某些事务协调过程未完成这时虽然业务代码“看起来已经执行完”但事务本身并没有真正成功结束因此结果通常仍然是整笔事务回滚。3. 提交后报错这是最容易引发误判的场景。假设事务中的写库操作已经全部成功提交数据库中已经能查到数据。随后又执行了一段“提交后逻辑”结果这段逻辑抛出了异常。这时通常意味着后续处理失败了当前调用可能会返回异常但前面已经提交的数据通常不会自动回滚原因很简单事务已经结束了。事务回滚的时机已经过去后续异常不会自动让数据库回到提交前状态。五、为什么“提交后报错”常常让人误解很多人在实际项目中看到过这样的现象某一步本地数据库已经写入成功后续又发生异常最终前面的数据也没有保留于是得出结论原来提交后报错也能回滚。这个理解并不完全准确。更准确地说往往是因为当前看到的“本地提交”并不是整笔业务的最终成功点。也就是说某个子步骤已经提交但外层整笔业务还没有最终完成如果外层统一事务最终失败这个子步骤的数据仍可能被整体撤回因此真正发生的不是“普通本地事务提交后又自动回滚”而是外层还有更大范围的事务控制在生效。六、普通事务与更大范围事务的区别下面这张图可以帮助理解这个差异成功失败外层业务开始本地步骤A写库本地步骤B写库某一步进入提交后逻辑外层整笔业务最终成功吗全部数据最终保留整笔业务被判失败前面中间步骤的数据仍可能被整体撤回这个场景说明某一步本地看似已经结束并不等于整条业务已经结束。因此在复杂系统中仅凭“这一步已经提交”来判断最终是否保留数据往往是不够的。七、结合实际案例来理解假设存在这样一条业务链路先更新订单签收状态再调用另一个服务写入 dictdict 写入完成后在“提交后执行”的逻辑中故意抛出异常此时很多人会自然地提出一个问题dict 都已经写入成功了后面再报错它还会回滚吗这个问题不能直接回答“会”或“不会”而需要分两层来判断。第一层如果只看普通本地事务如果这里只是一个普通事务那么结论通常是提交后逻辑里再抛异常不会把已经提交的数据自动回滚。因为本地事务已经完成数据库已经确认落库。此时后续异常只代表后续流程失败但前面数据通常仍然保留第二层如果外层还有更大范围的事务控制如果这条链路并不是普通单体事务而是由外层更大范围的事务统一控制那么情况就不同了。这时即使某一步看起来已经执行到“提交后逻辑”也不代表整条链路已经彻底成功。如果外层最终判定整笔业务失败那么结果可能是订单更新被撤回dict 写入也被撤回因此这里的关键点不是“提交后代码能不能回滚本地事务”而是整笔业务是否已经从更高层面真正完成。八、判断事务是否回滚的实用方法在实际排查问题时可以按照下面的顺序判断。提交前提交后是否出现异常异常发生在提交前还是提交后优先判断为可进入回滚阶段继续判断是否存在外层更大事务外层事务是否尚未结束整笔业务仍可能失败前面数据可能被撤回前面已提交数据通常不会自动回滚实际工作中这个判断顺序非常重要先看异常时间点再看本地事务是否已经结束再看是否存在外层统一事务最后区分同步链路和异步链路九、几个常见误区误区一只要抛异常就一定回滚错误。是否回滚不取决于“有没有异常”而取决于异常发生时事务是否尚未结束当前代码是否仍在事务控制范围内误区二本地提交成功就绝对不会再被撤回不完全正确。如果只是普通本地事务通常可以这样理解。但如果外层还有更大范围的事务控制那么本地这一步虽然先提交最终仍可能随整笔业务一起撤回。误区三看到“提交后执行”就等于整条业务已经彻底成功错误。“提交后执行”最多只能说明这一层本地事务已经提交。它并不一定代表整条业务链路已经从全局视角彻底完成。误区四同步报错和异步报错没有区别也不对。如果接口已经成功返回事务也已经结束之后异步任务再报错通常不会再回滚前面的同步写库结果。因此同步主链路异常和异步后置异常在事务结果上往往完全不是一回事。十、一个便于记忆的总结可以把事务问题浓缩成下面四句话提交前报错前面的数据通常会回滚。提交时失败前面的数据通常也会回滚。提交后报错前面的数据通常不会自动回滚。如果外层还有更大事务控制就不能只看本地是否提交还要看整笔业务最终是否成功。十一、结语事务回滚问题之所以容易被误解根本原因在于很多人在判断时只盯着“有没有报错”却忽略了事务的生命周期。真正应该关注的是异常发生时这笔事务到底结束了没有。如果事务尚未结束那么前面的操作通常还有机会被整体撤回如果事务已经真正提交完成那么后续异常通常只会影响后续流程而不会自动抹掉已经提交的数据。而在多服务、复杂链路场景下还必须进一步判断当前看到的“已提交”究竟是本地步骤的提交还是整笔业务的最终完成。只有把这几个层次区分清楚才能真正理解事务回滚到底回滚到哪一步。