Skip to content

事务提交前触发异步任务 ​

问题现象 ​

保存成功后异步通知偶尔读不到记录,或者主事务回滚了但消息已经发出。

适用环境 ​

Spring Boot 3,应用事件、@Async、关系型数据库。

本文是可复用的排障示例,不代表某次个人生产事故。命令中的名称、账号和数据均为示例,执行前请替换为已确认的目标。

复现或诊断步骤 ​

  1. 记录事务开始、事件发布、实际提交、异步读取的时间与业务 ID。
  2. 检查异步任务是否从 @Transactional 方法中直接提交给线程池。
  3. 制造事务延迟和回滚,观察消费者是否仍执行;不要用真实收件人验证。

原因分析 ​

异步线程不继承调用线程的事务上下文。在 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 同步回调里直接写数据,可能仍访问旧事务资源但无法再次提交,必须明确新事务边界。

官方参考链接 ​

相关分类:Java 后端 · 全部笔记