跳转到页面内容
圆点软件.YUANDIAN SOFTWARE

YUANDIAN / INSIGHTS & IDEAS

Spring Boot 事务边界的误用场景与修正方式

事务注解写了却不回滚、长事务占满连接池、并发更新互相覆盖,这些问题的根源多在事务边界划分,而不是数据库本身。

专注软件开发 · 连接业务与可能技术分享
Spring Boot 事务边界的误用场景与修正方式

事务失效的常见触发点

事务注解依赖代理生效,同一个类内部方法直接调用不会走代理,注解形同虚设。把方法拆到另一个受容器管理的组件,或者通过代理对象调用,才能生效。

方法不是公开的、类被声明为最终类、异常被自己捕获后没有重新抛出,都会让回滚不发生。默认只对运行时异常回滚,受检异常需要显式声明回滚类型。

多数据源场景下没有指定事务管理器,事务可能落在错误的库上。每个数据源配好独立的事务管理器,并在注解里明确指明。

控制事务的持有时长

事务里做远程调用、发消息、生成文件,会把数据库连接一直占着。外部依赖的耗时不可控,高峰期很容易把连接池耗光。

正确顺序是先做完所有本地校验和计算,事务里只保留必要的写操作。远程调用安排在事务提交之后,或者放到提交完成事件里处理。

需要跨多个写操作保持一致时,宁可把事务切小、用状态机补偿,也不要把一个长流程塞进单个事务。长事务带来的锁等待和回滚成本往往高于补偿逻辑。

传播行为的取舍

默认的传播行为会加入已有事务,嵌套调用时内层失败会让外层一起回滚。心里要清楚当前方法是被谁调用的。

新开事务的传播行为会挂起外层并另起一个,适合记录操作日志这类无论主流程成败都要落库的场景。它使用独立连接,注意连接池容量。

嵌套事务依赖保存点,只在部分数据库和驱动上支持得完整。跨库或者用了连接池中间件时,保存点的行为需要实测确认。

并发下的锁与幂等

事务内先查后改是典型的并发漏洞,两个请求可能都读到旧值再各自覆盖。用悲观锁的行锁,或者乐观锁的版本号字段来约束。

乐观锁适合冲突概率低的场景,冲突时抛异常由业务重试。重试次数要设上限,并确保重试逻辑幂等,否则可能重复扣减。

唯一索引是最后一道防线。先靠数据库约束保证不重复,业务层再按约束冲突做友好提示,比只依赖查询判断可靠。

缓存配合与回滚验证

缓存更新通常安排在事务提交之后。事务还没提交就删缓存,其他请求可能把旧值又读回来写进缓存。

用事务同步回调或者监听提交完成事件来触发缓存失效。删除失败要有重试或过期兜底,不能让缓存长期停留在旧数据。涉及金额、库存的数据宁可多查一次数据库,也不要在事务提交前更新缓存。

写测试时主动让事务内某一步抛异常,断言数据库里没有留下任何写入。只看接口返回失败不够,要落到数据层核对。

对批量写入的接口,构造中间一条数据违反约束的情况,确认整批都被回滚而不是部分成功。同时确认失败请求返回的错误信息不包含内部细节。

Java事务

LET’S BUILD TOGETHER

让思考,成为下一步行动。

把业务场景与初步想法告诉我们,一起找到合适的实现方式。