微操征霸
351.42M · 2026-02-04
“订单重复创建!用户投诉炸了!”
去年双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默认不启用且存在兼容风险。
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// 事务失效!this调用绕过代理
this.deductStock(order);
}
@Transactional
public void deductStock(Order order) { ... }
}
救命方案:
// 方案1:注入自身(推荐)
@Autowired private OrderService self;
self.deductStock(order);
// 方案2: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() { ... }
// 显式指定(结合@Primary合理规划)
@Transactional(transactionManager = "orderTransactionManager")
血泪建议:多数据源项目务必在启动日志中搜索Transaction manager确认绑定关系!
// 异步方法内开启新线程,原事务上下文丢失
@Async
@Transactional
public void sendNotify(Order order) { ... } // 事务失效!
正确姿势:
@Transactional(propagation = Propagation.REQUIRES_NEW))| 坑位 | 现象 | 一句话解法 |
|---|---|---|
| 坑6 | MyISAM表操作无回滚 | 检查MySQL引擎:SHOW TABLE STATUS |
| 坑7 | 传播行为误用(如REQUIRES_NEW嵌套) | 画事务边界图!避免不必要嵌套 |
| 坑8 | 事务超时设置过短 | @Transactional(timeout=30) 结合慢SQL优化 |
| 坑9 | 测试环境H2内存库事务行为差异 | 用Testcontainers跑真实DB测试 |
| 坑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 [...]日志链
压测验证
@Transactional方法耗时去年故障复盘会上,我贴出这张检查清单后,团队再未因事务问题深夜报警。技术人的成长,往往藏在这些“本可以避免”的细节里。
你在事务上栽过最深的跟头是什么?