事务回滚到底发生在什么时候
在日常开发中,关于事务最常见的误解之一是:
只要后续代码报错,前面已经写入数据库的数据就一定会回滚。
这句话并不准确。
事务是否回滚,并不取决于"有没有异常",而取决于一个更关键的问题:
异常发生时,事务是否还处于可回滚阶段。
本文围绕这个核心问题展开,重点说明以下几个场景:
- 提交前报错,是否回滚
- 提交时报错,是否回滚
- 提交后报错,是否回滚
- 多服务场景下,为什么"已经提交"的数据有时仍然会被撤回
一、先给出结论
可以先记住下面这组结论:
- 事务提交前发生异常,前面已经执行的写库操作通常会回滚。
- 事务提交过程中失败,整笔事务通常也会回滚。
- 事务已经提交完成后,再发生异常,前面已经提交的数据通常不会自动回滚。
- 如果系统外层还存在更大范围的事务控制,那么某一步本地已提交的数据,最终仍可能被整体撤回。
因此,判断事务是否回滚,最重要的不是看"是否抛异常",而是看:
异常发生时,事务究竟结束了没有。
二、事务回滚的本质
事务的核心目标,是把一组操作当成一个整体来处理。
这个整体在最终结果上只有两种状态:
- 要么全部成功
- 要么全部不生效
但这里有一个前提:
这组操作仍然处于事务控制范围之内。
一旦事务已经真正提交完成,数据库已经确认落库,那么后面即使再抛出异常,这个异常也已经不再属于"当前事务的回滚阶段",自然也无法把已经提交的数据自动撤销。
三、用一张图看懂事务回滚时机
#mermaid-svg-NqlDqfqykYZbiIEt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NqlDqfqykYZbiIEt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NqlDqfqykYZbiIEt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NqlDqfqykYZbiIEt .error-icon{fill:#552222;}#mermaid-svg-NqlDqfqykYZbiIEt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NqlDqfqykYZbiIEt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NqlDqfqykYZbiIEt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NqlDqfqykYZbiIEt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NqlDqfqykYZbiIEt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NqlDqfqykYZbiIEt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NqlDqfqykYZbiIEt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NqlDqfqykYZbiIEt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NqlDqfqykYZbiIEt .marker.cross{stroke:#333333;}#mermaid-svg-NqlDqfqykYZbiIEt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NqlDqfqykYZbiIEt p{margin:0;}#mermaid-svg-NqlDqfqykYZbiIEt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NqlDqfqykYZbiIEt .cluster-label text{fill:#333;}#mermaid-svg-NqlDqfqykYZbiIEt .cluster-label span{color:#333;}#mermaid-svg-NqlDqfqykYZbiIEt .cluster-label span p{background-color:transparent;}#mermaid-svg-NqlDqfqykYZbiIEt .label text,#mermaid-svg-NqlDqfqykYZbiIEt span{fill:#333;color:#333;}#mermaid-svg-NqlDqfqykYZbiIEt .node rect,#mermaid-svg-NqlDqfqykYZbiIEt .node circle,#mermaid-svg-NqlDqfqykYZbiIEt .node ellipse,#mermaid-svg-NqlDqfqykYZbiIEt .node polygon,#mermaid-svg-NqlDqfqykYZbiIEt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NqlDqfqykYZbiIEt .rough-node .label text,#mermaid-svg-NqlDqfqykYZbiIEt .node .label text,#mermaid-svg-NqlDqfqykYZbiIEt .image-shape .label,#mermaid-svg-NqlDqfqykYZbiIEt .icon-shape .label{text-anchor:middle;}#mermaid-svg-NqlDqfqykYZbiIEt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NqlDqfqykYZbiIEt .rough-node .label,#mermaid-svg-NqlDqfqykYZbiIEt .node .label,#mermaid-svg-NqlDqfqykYZbiIEt .image-shape .label,#mermaid-svg-NqlDqfqykYZbiIEt .icon-shape .label{text-align:center;}#mermaid-svg-NqlDqfqykYZbiIEt .node.clickable{cursor:pointer;}#mermaid-svg-NqlDqfqykYZbiIEt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NqlDqfqykYZbiIEt .arrowheadPath{fill:#333333;}#mermaid-svg-NqlDqfqykYZbiIEt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NqlDqfqykYZbiIEt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NqlDqfqykYZbiIEt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NqlDqfqykYZbiIEt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NqlDqfqykYZbiIEt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NqlDqfqykYZbiIEt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NqlDqfqykYZbiIEt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NqlDqfqykYZbiIEt .cluster text{fill:#333;}#mermaid-svg-NqlDqfqykYZbiIEt .cluster span{color:#333;}#mermaid-svg-NqlDqfqykYZbiIEt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NqlDqfqykYZbiIEt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NqlDqfqykYZbiIEt rect.text{fill:none;stroke-width:0;}#mermaid-svg-NqlDqfqykYZbiIEt .icon-shape,#mermaid-svg-NqlDqfqykYZbiIEt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NqlDqfqykYZbiIEt .icon-shape p,#mermaid-svg-NqlDqfqykYZbiIEt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NqlDqfqykYZbiIEt .icon-shape .label rect,#mermaid-svg-NqlDqfqykYZbiIEt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NqlDqfqykYZbiIEt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NqlDqfqykYZbiIEt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NqlDqfqykYZbiIEt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 提交前
提交时
提交后
开始执行业务
执行写库操作
异常发生在什么时候?
事务尚未结束
前面写入的数据
通常会一起回滚
提交未真正成功
整笔事务通常仍会回滚
事务已经完成
后续异常只表示
后续动作失败
前面已提交的数据
通常不会自动回滚
这张图表达的是事务判断中最关键的一条主线:
只有在事务尚未真正结束时,异常才有机会触发整体回滚。
四、三种最典型的事务场景
1. 提交前报错
这是最常见的事务回滚场景。
假设一个方法中包含两步数据库操作:
- 更新订单状态
- 插入一条字典数据
如果第二步执行时报错,而整个事务尚未提交,那么通常结果是:
- 订单状态更新回滚
- 字典插入回滚
也就是说,这两步都不会最终生效。
这也是大多数开发者最熟悉的事务行为。
2. 提交过程中失败
有些场景下,代码本身已经执行到尾部,但数据库在真正提交时失败了。
例如:
- 提交阶段发生数据库异常
- 提交时被事务框架判定为失败
- 某些事务协调过程未完成
这时虽然业务代码"看起来已经执行完",但事务本身并没有真正成功结束,因此结果通常仍然是:
整笔事务回滚。
3. 提交后报错
这是最容易引发误判的场景。
假设事务中的写库操作已经全部成功提交,数据库中已经能查到数据。随后又执行了一段"提交后逻辑",结果这段逻辑抛出了异常。
这时通常意味着:
- 后续处理失败了
- 当前调用可能会返回异常
- 但前面已经提交的数据,通常不会自动回滚
原因很简单:
事务已经结束了。
事务回滚的时机已经过去,后续异常不会自动让数据库回到提交前状态。
五、为什么"提交后报错"常常让人误解
很多人在实际项目中看到过这样的现象:
- 某一步本地数据库已经写入成功
- 后续又发生异常
- 最终前面的数据也没有保留
于是得出结论:
原来提交后报错也能回滚。
这个理解并不完全准确。
更准确地说,往往是因为:
当前看到的"本地提交",并不是整笔业务的最终成功点。
也就是说:
- 某个子步骤已经提交
- 但外层整笔业务还没有最终完成
- 如果外层统一事务最终失败,这个子步骤的数据仍可能被整体撤回
因此,真正发生的不是"普通本地事务提交后又自动回滚",而是:
外层还有更大范围的事务控制在生效。
六、普通事务与更大范围事务的区别
下面这张图可以帮助理解这个差异:
#mermaid-svg-7MqL8YuX32WwPxuz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7MqL8YuX32WwPxuz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7MqL8YuX32WwPxuz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7MqL8YuX32WwPxuz .error-icon{fill:#552222;}#mermaid-svg-7MqL8YuX32WwPxuz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7MqL8YuX32WwPxuz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7MqL8YuX32WwPxuz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7MqL8YuX32WwPxuz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7MqL8YuX32WwPxuz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7MqL8YuX32WwPxuz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7MqL8YuX32WwPxuz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7MqL8YuX32WwPxuz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7MqL8YuX32WwPxuz .marker.cross{stroke:#333333;}#mermaid-svg-7MqL8YuX32WwPxuz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7MqL8YuX32WwPxuz p{margin:0;}#mermaid-svg-7MqL8YuX32WwPxuz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7MqL8YuX32WwPxuz .cluster-label text{fill:#333;}#mermaid-svg-7MqL8YuX32WwPxuz .cluster-label span{color:#333;}#mermaid-svg-7MqL8YuX32WwPxuz .cluster-label span p{background-color:transparent;}#mermaid-svg-7MqL8YuX32WwPxuz .label text,#mermaid-svg-7MqL8YuX32WwPxuz span{fill:#333;color:#333;}#mermaid-svg-7MqL8YuX32WwPxuz .node rect,#mermaid-svg-7MqL8YuX32WwPxuz .node circle,#mermaid-svg-7MqL8YuX32WwPxuz .node ellipse,#mermaid-svg-7MqL8YuX32WwPxuz .node polygon,#mermaid-svg-7MqL8YuX32WwPxuz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7MqL8YuX32WwPxuz .rough-node .label text,#mermaid-svg-7MqL8YuX32WwPxuz .node .label text,#mermaid-svg-7MqL8YuX32WwPxuz .image-shape .label,#mermaid-svg-7MqL8YuX32WwPxuz .icon-shape .label{text-anchor:middle;}#mermaid-svg-7MqL8YuX32WwPxuz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7MqL8YuX32WwPxuz .rough-node .label,#mermaid-svg-7MqL8YuX32WwPxuz .node .label,#mermaid-svg-7MqL8YuX32WwPxuz .image-shape .label,#mermaid-svg-7MqL8YuX32WwPxuz .icon-shape .label{text-align:center;}#mermaid-svg-7MqL8YuX32WwPxuz .node.clickable{cursor:pointer;}#mermaid-svg-7MqL8YuX32WwPxuz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7MqL8YuX32WwPxuz .arrowheadPath{fill:#333333;}#mermaid-svg-7MqL8YuX32WwPxuz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7MqL8YuX32WwPxuz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7MqL8YuX32WwPxuz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7MqL8YuX32WwPxuz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7MqL8YuX32WwPxuz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7MqL8YuX32WwPxuz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7MqL8YuX32WwPxuz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7MqL8YuX32WwPxuz .cluster text{fill:#333;}#mermaid-svg-7MqL8YuX32WwPxuz .cluster span{color:#333;}#mermaid-svg-7MqL8YuX32WwPxuz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7MqL8YuX32WwPxuz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7MqL8YuX32WwPxuz rect.text{fill:none;stroke-width:0;}#mermaid-svg-7MqL8YuX32WwPxuz .icon-shape,#mermaid-svg-7MqL8YuX32WwPxuz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7MqL8YuX32WwPxuz .icon-shape p,#mermaid-svg-7MqL8YuX32WwPxuz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7MqL8YuX32WwPxuz .icon-shape .label rect,#mermaid-svg-7MqL8YuX32WwPxuz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7MqL8YuX32WwPxuz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7MqL8YuX32WwPxuz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7MqL8YuX32WwPxuz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 成功
失败
外层业务开始
本地步骤A写库
本地步骤B写库
某一步进入提交后逻辑
外层整笔业务最终成功吗?
全部数据最终保留
整笔业务被判失败
前面中间步骤的数据
仍可能被整体撤回
这个场景说明:
某一步本地看似已经结束,并不等于整条业务已经结束。
因此,在复杂系统中,仅凭"这一步已经提交"来判断最终是否保留数据,往往是不够的。
七、结合实际案例来理解
假设存在这样一条业务链路:
- 先更新订单签收状态
- 再调用另一个服务写入 dict
- dict 写入完成后,在"提交后执行"的逻辑中故意抛出异常
此时很多人会自然地提出一个问题:
dict 都已经写入成功了,后面再报错,它还会回滚吗?
这个问题不能直接回答"会"或"不会",而需要分两层来判断。
第一层:如果只看普通本地事务
如果这里只是一个普通事务,那么结论通常是:
提交后逻辑里再抛异常,不会把已经提交的数据自动回滚。
因为本地事务已经完成,数据库已经确认落库。
此时后续异常只代表:
- 后续流程失败
- 但前面数据通常仍然保留
第二层:如果外层还有更大范围的事务控制
如果这条链路并不是普通单体事务,而是由外层更大范围的事务统一控制,那么情况就不同了。
这时即使某一步看起来已经执行到"提交后逻辑",也不代表整条链路已经彻底成功。
如果外层最终判定整笔业务失败,那么结果可能是:
- 订单更新被撤回
- dict 写入也被撤回
因此,这里的关键点不是"提交后代码能不能回滚本地事务",而是:
整笔业务是否已经从更高层面真正完成。
八、判断事务是否回滚的实用方法
在实际排查问题时,可以按照下面的顺序判断。
#mermaid-svg-bUZUDACmRC5Z0Ss0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bUZUDACmRC5Z0Ss0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .error-icon{fill:#552222;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .marker.cross{stroke:#333333;}#mermaid-svg-bUZUDACmRC5Z0Ss0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bUZUDACmRC5Z0Ss0 p{margin:0;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .cluster-label text{fill:#333;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .cluster-label span{color:#333;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .cluster-label span p{background-color:transparent;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .label text,#mermaid-svg-bUZUDACmRC5Z0Ss0 span{fill:#333;color:#333;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .node rect,#mermaid-svg-bUZUDACmRC5Z0Ss0 .node circle,#mermaid-svg-bUZUDACmRC5Z0Ss0 .node ellipse,#mermaid-svg-bUZUDACmRC5Z0Ss0 .node polygon,#mermaid-svg-bUZUDACmRC5Z0Ss0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .rough-node .label text,#mermaid-svg-bUZUDACmRC5Z0Ss0 .node .label text,#mermaid-svg-bUZUDACmRC5Z0Ss0 .image-shape .label,#mermaid-svg-bUZUDACmRC5Z0Ss0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .rough-node .label,#mermaid-svg-bUZUDACmRC5Z0Ss0 .node .label,#mermaid-svg-bUZUDACmRC5Z0Ss0 .image-shape .label,#mermaid-svg-bUZUDACmRC5Z0Ss0 .icon-shape .label{text-align:center;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .node.clickable{cursor:pointer;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .arrowheadPath{fill:#333333;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bUZUDACmRC5Z0Ss0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bUZUDACmRC5Z0Ss0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bUZUDACmRC5Z0Ss0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .cluster text{fill:#333;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .cluster span{color:#333;}#mermaid-svg-bUZUDACmRC5Z0Ss0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bUZUDACmRC5Z0Ss0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .icon-shape,#mermaid-svg-bUZUDACmRC5Z0Ss0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .icon-shape p,#mermaid-svg-bUZUDACmRC5Z0Ss0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .icon-shape .label rect,#mermaid-svg-bUZUDACmRC5Z0Ss0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bUZUDACmRC5Z0Ss0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bUZUDACmRC5Z0Ss0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bUZUDACmRC5Z0Ss0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 提交前
提交后
是
否
出现异常
异常发生在提交前还是提交后?
优先判断为
可进入回滚阶段
继续判断是否存在
外层更大事务
外层事务是否尚未结束?
整笔业务仍可能失败
前面数据可能被撤回
前面已提交数据
通常不会自动回滚
实际工作中,这个判断顺序非常重要:
- 先看异常时间点
- 再看本地事务是否已经结束
- 再看是否存在外层统一事务
- 最后区分同步链路和异步链路
九、几个常见误区
误区一:只要抛异常就一定回滚
错误。
是否回滚,不取决于"有没有异常",而取决于:
- 异常发生时事务是否尚未结束
- 当前代码是否仍在事务控制范围内
误区二:本地提交成功就绝对不会再被撤回
不完全正确。
如果只是普通本地事务,通常可以这样理解。
但如果外层还有更大范围的事务控制,那么本地这一步虽然先提交,最终仍可能随整笔业务一起撤回。
误区三:看到"提交后执行"就等于整条业务已经彻底成功
错误。
"提交后执行"最多只能说明:
这一层本地事务已经提交。
它并不一定代表:
整条业务链路已经从全局视角彻底完成。
误区四:同步报错和异步报错没有区别
也不对。
如果接口已经成功返回,事务也已经结束,之后异步任务再报错,通常不会再回滚前面的同步写库结果。
因此,同步主链路异常和异步后置异常,在事务结果上往往完全不是一回事。
十、一个便于记忆的总结
可以把事务问题浓缩成下面四句话:
- 提交前报错,前面的数据通常会回滚。
- 提交时失败,前面的数据通常也会回滚。
- 提交后报错,前面的数据通常不会自动回滚。
- 如果外层还有更大事务控制,就不能只看本地是否提交,还要看整笔业务最终是否成功。
十一、结语
事务回滚问题之所以容易被误解,根本原因在于很多人在判断时只盯着"有没有报错",却忽略了事务的生命周期。
真正应该关注的是:
异常发生时,这笔事务到底结束了没有。
如果事务尚未结束,那么前面的操作通常还有机会被整体撤回;如果事务已经真正提交完成,那么后续异常通常只会影响后续流程,而不会自动抹掉已经提交的数据。
而在多服务、复杂链路场景下,还必须进一步判断:
当前看到的"已提交",究竟是本地步骤的提交,还是整笔业务的最终完成。
只有把这几个层次区分清楚,才能真正理解事务回滚到底回滚到哪一步。