
事务失效的常见触发点
事务注解依赖代理生效,同一个类内部方法直接调用不会走代理,注解形同虚设。把方法拆到另一个受容器管理的组件,或者通过代理对象调用,才能生效。
方法不是公开的、类被声明为最终类、异常被自己捕获后没有重新抛出,都会让回滚不发生。默认只对运行时异常回滚,受检异常需要显式声明回滚类型。
多数据源场景下没有指定事务管理器,事务可能落在错误的库上。每个数据源配好独立的事务管理器,并在注解里明确指明。
控制事务的持有时长
事务里做远程调用、发消息、生成文件,会把数据库连接一直占着。外部依赖的耗时不可控,高峰期很容易把连接池耗光。
正确顺序是先做完所有本地校验和计算,事务里只保留必要的写操作。远程调用安排在事务提交之后,或者放到提交完成事件里处理。
需要跨多个写操作保持一致时,宁可把事务切小、用状态机补偿,也不要把一个长流程塞进单个事务。长事务带来的锁等待和回滚成本往往高于补偿逻辑。
传播行为的取舍
默认的传播行为会加入已有事务,嵌套调用时内层失败会让外层一起回滚。心里要清楚当前方法是被谁调用的。
新开事务的传播行为会挂起外层并另起一个,适合记录操作日志这类无论主流程成败都要落库的场景。它使用独立连接,注意连接池容量。
嵌套事务依赖保存点,只在部分数据库和驱动上支持得完整。跨库或者用了连接池中间件时,保存点的行为需要实测确认。
并发下的锁与幂等
事务内先查后改是典型的并发漏洞,两个请求可能都读到旧值再各自覆盖。用悲观锁的行锁,或者乐观锁的版本号字段来约束。
乐观锁适合冲突概率低的场景,冲突时抛异常由业务重试。重试次数要设上限,并确保重试逻辑幂等,否则可能重复扣减。
唯一索引是最后一道防线。先靠数据库约束保证不重复,业务层再按约束冲突做友好提示,比只依赖查询判断可靠。
缓存配合与回滚验证
缓存更新通常安排在事务提交之后。事务还没提交就删缓存,其他请求可能把旧值又读回来写进缓存。
用事务同步回调或者监听提交完成事件来触发缓存失效。删除失败要有重试或过期兜底,不能让缓存长期停留在旧数据。涉及金额、库存的数据宁可多查一次数据库,也不要在事务提交前更新缓存。
写测试时主动让事务内某一步抛异常,断言数据库里没有留下任何写入。只看接口返回失败不够,要落到数据层核对。
对批量写入的接口,构造中间一条数据违反约束的情况,确认整批都被回滚而不是部分成功。同时确认失败请求返回的错误信息不包含内部细节。


