原文来自于:zha-ge.cn/java/111
事务崩了别怪数据库!三大核心要素没掌握才是根本原因
说真的,每次上线修 Bug 遇到"事务失效"这四个字,我都想立马上楼顶冷静一下。数据库被程序员骂了无数次,但不少时候还真不能怪它------大多数雷,都是我们自己埋下的。
上周同事阿伟又一次喊我:"快救命!数据库的事务不起作用,全表更新没回滚,库存炸啦!"哈哈,他这口气像极了觉得自己一点问题都没有,锅都是 MySQL 的锅。我淡定问了句:"你是不是只知道 commit/rollback,三大要素你搞清楚了没?"
他一脸懵逼:"啥三大要素?"------好家伙,这就是故事的开始。
那些年,我们踩过的事务"坑"
其实 Java 世界说到事务,基本都是和 Spring @Transactional 打交道的,我总结了下,大家踩雷最密集的,有这么几个典型瞬间:
- 隔离级别?"反正默认就行吧"------结果脏读、幻读满天飞。
- 传播机制?"我外层有@Transactional就行"------结果内层事务意外失效。
- 回滚机制?"抛了异常不是自动回滚吗?"------结果 checked exception 扑面而来,程序一脸懵。
我问阿伟:"你是不是下面这种典型用法?" 他翻出代码,果然如此朴实无华:
java
@Transactional
public void updateStock() {
// ...更新多张表
if (xxx) {
throw new Exception("看似会回滚......");
}
}
槽点来了,所以说"抛异常自动回滚",你确定吗?其实只有 RuntimeException(也就是未检查异常)才会自动触发 Spring 的回滚逻辑,普通 Exception 可没这待遇。
踩坑瞬间
那天帮阿伟查 Bug,咱们上真家伙:
- 问题核心1:checked exception
- 他抛的是 Exception(checked),Spring 并不会自动 rollback。
- 解决办法:
@Transactional(rollbackFor = Exception.class)
- 问题核心2:自行catch住例外
- 代码里 try-catch 把所有异常吞了,Spring 当然检测不到异常发生,事务照常提交。
- 问题核心3:不懂传播机制
- 方法自己调用自己,@Transactional 无效......Spring AOP 懂的都懂。
有图有真相,主要地方关键几行:
java
@Transactional(rollbackFor = Exception.class)
public void outerMethod() {
try {
innerMethod();
} catch (Exception e) {
// 没抛出,没回滚
log.error("被我 catch 住了");
}
}
啧啧,各种防不胜防。
经验启示
后来,阿伟终于明白这三大核心要素了:
要素 | 懒人误区 | 你该咋搞 |
---|---|---|
隔离级别 | "默认就行!" | 了解脏读、幻读风险 |
传播机制 | "随便加个注解" | 明白 REQUIRED/REQUIRES_NEW 区别 |
回滚机制 | "随便抛个异常" | Runtime、自定 rollbackFor、避免异常被吞 |
所以别再张口闭口"数据库背锅,MySQL太坑",其实各种事务悬案,80%是自己代码写崩的。
讲完了,收个尾巴: 踩坑不可怕,搞明白才是大佬。下一次,事务失效不要再冤枉你家数据库,先照照镜子想想,是不是自己三大要素没掌握。最后,用一句朋友总结的话:"你只要敢乱用@Transactional,坑就会如影随形。"
下班,溜了~