深浅主题
事务提交前触发异步任务
问题现象
保存成功后异步通知偶尔读不到记录,或者主事务回滚了但消息已经发出。
适用环境
Spring Boot 3,应用事件、@Async、关系型数据库。
本文是可复用的排障示例,不代表某次个人生产事故。命令中的名称、账号和数据均为示例,执行前请替换为已确认的目标。
复现或诊断步骤
- 记录事务开始、事件发布、实际提交、异步读取的时间与业务 ID。
- 检查异步任务是否从 @Transactional 方法中直接提交给线程池。
- 制造事务延迟和回滚,观察消费者是否仍执行;不要用真实收件人验证。
原因分析
异步线程不继承调用线程的事务上下文。在 READ COMMITTED 等隔离级别下,它可能早于主事务提交读取数据库。提交后事件能避免这个先后问题,但进程崩溃仍可能让事件丢失。
修复示例
java
public record OrderCreated(long id) {}
// 在已有事务方法里调用 events.publishEvent(new OrderCreated(id));
@Component
public class OrderListener {
@Async("notifyExecutor")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onCreated(OrderCreated event) {
// 读取已提交记录,按业务 ID 幂等处理
}
}确保启用异步支持并定义有界 notifyExecutor。发布者没有事务时,默认监听器不会执行;不要随意启用 fallbackExecution 改变业务语义。必须可靠投递时,在同一事务写入 outbox 表,再由可重试工作器读取;监听器内需要写库时使用独立事务服务。
验证方法
验证正常提交只处理一次业务结果、回滚不处理、重复投递无副作用。模拟重启和线程池拒绝,检查 outbox 重试与积压报警;普通应用事件不能被描述为可靠消息队列。
常见误区
睡眠几百毫秒不构成提交保证。AFTER_COMMIT 同步回调里直接写数据,可能仍访问旧事务资源但无法再次提交,必须明确新事务边界。