线上有一类问题特别折磨人,开发和测试都很难复现,只有生产环境偶尔抽风。
前端调用接口完全正常,返回码正确、业务状态成功、日志打印写入成功,但是去数据库一查,数据偶尔凭空消失,压根没存进去。
大部分人第一反应是重试、前端重复提交、数据库超时,排查一圈最后啥也找不到。其实这类隐形丢数据问题,多半和事务隐性回滚、提交时机错乱有关。
很多新手包括做了几年的后端,对事务的理解都停留在:加个注解,方法成功就提交,报错就回滚。
真实线上场景远比这复杂。
先说最常见的坑:内部异常被吞,事务悄悄回滚。
事务方法里,如果 try-catch 把异常捕获了,没有抛出,框架判定方法正常结束。很多人以为这样就万事大吉,殊不知代码里出现过数据库操作异常、约束冲突、字段超长、锁等待超时。
异常被你吞了、日志没打全、业务继续往下走,最后接口返回成功。但事务在底层已经标记为回滚,方法结束直接放弃写入,数据直接丢失。
最坑的是全程无报错,看起来完美运行,就是不存数据。
第二个高频问题:事务内异步操作。
很多人喜欢在事务里开线程、调异步任务、发消息队列。主线程还在事务里执行写入,异步线程还没跑完,主线程方法已经走完提交事务。
时序一旦错开,异步读写数据库的时候,事务已经关闭,或者还没提交。
结果就是:主数据有时候存上、有时候丢失,异步读取时而查到数据、时而查不到,完全随机。
本地单机测试时序稳定,百分百没问题。线上高并发、线程调度随机,问题直接暴露。
还有一个极少有人注意的细节:查询操作打乱事务隔离级别。
同一个事务里,先查库、再计算、再更新写入。如果中间穿插大量查询,配合可重复读、读已提交的隔离级别,会出现快照数据滞后。
基于旧快照数据做判断,执行更新写入,逻辑看着没问题,最终却没有执行入库或者更新失效。
很多数据更新偶尔不生效,根源就在这。
另外就是事务传播机制乱套。
大事务嵌套小事务,外层没异常、内层抛异常回滚。内层回滚标记会传染外层事务。最后外层明明正常执行完毕,依然被强制整体回滚,数据直接清空。
这种嵌套事务问题,排查难度极高,日志看不出任何毛病,业务链路完全正常,就是不落地数据。
我线上踩过最无语的一次,是第三方SDK内部吞异常。
事务方法里调用第三方工具类,底层数据库操作失败,SDK内部try住了,没往外抛。业务代码正常走完,事务底层标记回滚,最终结果就是:接口成功、逻辑成功、数据库无数据。
排查了整整一天才定位到是第三方包静默吃掉异常。
久而久之我养成了一个习惯:
事务代码绝不随便吞异常,必须打印完整堆栈日志;事务内部绝不随意写异步逻辑;嵌套调用严格控制传播属性;关键写入操作之后,一定要校验数据库真实结果,不能只信代码执行结果。
很多线上诡异的数据丢失问题,不是代码bug,是对事务底层执行机制不熟悉。
表面看着稳如泰山,实则随时可能隐性回滚,悄无声息丢掉业务数据。