以下是对原文内容的重写,保留原意不变,语言风格更凝练、逻辑更清晰,同时维持技术细节的准确性与表达的生动性:
一、凌晨三点的警报
“订单重复创建!用户投诉炸了!”
去年双11零点17分,手机疯狂震动。监控大屏上刺目的红色曲线中,我死死盯着日志里那行:
Transaction silently rolled back because it has been marked as rollback-only,冷汗浸透掌心。
经过72小时不眠不休的排查,真相令人扼腕:一个 @Async 注解与自调用组合,竟让事务在无声无息中失效。
今天,我将自己踩过的坑、熬过的夜、总结出的排查清单,毫无保留地分享给你。
二、高频事务失效场景详解(附修复方案)
🔥 坑点一:方法修饰符不当
// ❌ 事务失效!Spring AOP仅支持 public 方法
@Transactional
protected void updateStock(Long skuId) { ... }
// ✅ 修复:改为 public,并避免 final/static 修饰
@Transactional
public void updateStock(Long skuId) { ... }
原理:JDK 动态代理只能拦截 public 方法;CGLIB 虽可代理非 public 方法,但 Spring 默认未启用,且存在兼容性隐患。
💣 坑点二:this 自调用绕过代理(高发事故!)
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// ❌ this 调用跳过代理,事务失效
this.deductStock(order);
}
@Transactional
public void deductStock(Order order) { ... }
}
解决方案:
-
推荐:注入自身 Bean
@Autowired private OrderService self; self.deductStock(order); - 备选:通过
AopContext.currentProxy()(需开启@EnableAspectJAutoProxy(exposeProxy = true))((OrderService) AopContext.currentProxy()).deductStock(order);
🌪️ 坑点三:异常被捕获导致“静默提交”
@Transactional
public void pay(Order order) {
try {
accountService.debit(order); // 可能抛出 BusinessException
} catch (Exception e) {
log.error("扣款异常", e); // ❌ 异常被吞,事务不回滚!
throw new RuntimeException(e); // ✅ 必须重新抛出
}
}
关键配置:
// 显式指定回滚异常类型(默认仅对 RuntimeException/Error 回滚)
@Transactional(rollbackFor = Exception.class)
🌐 坑点四:多数据源下的事务管理器错配
// ❌ 未指定事务管理器,可能绑定到错误数据源
@Transactional
public void syncData() { ... }
// ✅ 显式指定事务管理器
@Transactional(transactionManager = "orderTransactionManager")
建议:启动时务必检查日志中的 Transaction manager 输出,确认绑定关系正确!
⚡ 坑点五:@Async 与事务的上下文冲突
// ❌ 异步方法在新线程执行,原事务上下文丢失
@Async
@Transactional
public void sendNotify(Order order) { ... } // 事务无效!
正确做法:
- 主流程提交后再触发异步(如发布
ApplicationEvent) - 异步方法内独立开启新事务:
@Async @Transactional(propagation = Propagation.REQUIRES_NEW) public void sendNotify(Order order) { ... }
📌 其他高频问题速查表
| 编号 | 问题现象 | 解决方案 |
|---|---|---|
| 坑6 | MyISAM 表无法回滚 | 执行 SHOW TABLE STATUS,确保使用 InnoDB |
| 坑7 | 传播行为误用(如 REQUIRES_NEW 嵌套) | 绘制事务边界图,避免无谓嵌套 |
| 坑8 | 事务超时过短 | 设置 @Transactional(timeout = 30) 并优化慢 SQL |
| 坑9 | H2 测试环境事务行为不符 | 使用 Testcontainers 启动真实数据库 |
| 坑10 | 事务方法被 final/static 修饰 |
安装 IDEA 插件 “Spring Transaction Check” 进行编译期检查 |
三、生产级防御清单(已落地验证)
-
启动时自动扫描
@Component public class TransactionChecker implements CommandLineRunner { @Override public void run(String... args) { // 扫描 @Transactional 方法,校验修饰符、异常配置等 // 输出报告并阻断启动(关键系统必备) } } -
事务日志埋点
<!-- logback-spring.xml --> <logger name="org.springframework.transaction.interceptor" level="TRACE"/>关键观察:
Getting transaction for [...]与Completing transaction for [...]日志是否成对出现。 - 压测与验证
- 模拟异常,验证数据一致性
- 使用 Arthas 监控
@Transactional方法耗时,识别慢事务
四、灵魂总结
✨ 事务不是魔法,而是一份契约
- 它依赖代理机制,请敬畏 AOP 的边界
- 它需要显式声明,拒绝“我以为它会回滚”
- 它必须被验证,测试覆盖远胜文档承诺
这份经验,愿你少走弯路,不再深夜惊醒。
随时随地看视频