事务崩了别怪数据库!三大核心要素没掌握才是根本原因

131 阅读3分钟

原文来自于:zha-ge.cn/java/111

事务崩了别怪数据库!三大核心要素没掌握才是根本原因

说真的,每次上线修 Bug 遇到“事务失效”这四个字,我都想立马上楼顶冷静一下。数据库被程序员骂了无数次,但不少时候还真不能怪它——大多数雷,都是我们自己埋下的。

上周同事阿伟又一次喊我:“快救命!数据库的事务不起作用,全表更新没回滚,库存炸啦!”哈哈,他这口气像极了觉得自己一点问题都没有,锅都是 MySQL 的锅。我淡定问了句:“你是不是只知道 commit/rollback,三大要素你搞清楚了没?”

他一脸懵逼:“啥三大要素?”——好家伙,这就是故事的开始。


那些年,我们踩过的事务“坑”

其实 Java 世界说到事务,基本都是和 Spring @Transactional 打交道的,我总结了下,大家踩雷最密集的,有这么几个典型瞬间:

  • 隔离级别?“反正默认就行吧”——结果脏读、幻读满天飞。
  • 传播机制?“我外层有@Transactional就行”——结果内层事务意外失效。
  • 回滚机制?“抛了异常不是自动回滚吗?”——结果 checked exception 扑面而来,程序一脸懵。

我问阿伟:“你是不是下面这种典型用法?” 他翻出代码,果然如此朴实无华:

@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 懂的都懂。

有图有真相,主要地方关键几行:

@Transactional(rollbackFor = Exception.class)
public void outerMethod() {
    try {
        innerMethod();
    } catch (Exception e) {
        // 没抛出,没回滚
        log.error("被我 catch 住了");
    }
}

啧啧,各种防不胜防。


经验启示

后来,阿伟终于明白这三大核心要素了:

要素懒人误区你该咋搞
隔离级别“默认就行!”了解脏读、幻读风险
传播机制“随便加个注解”明白 REQUIRED/REQUIRES_NEW 区别
回滚机制“随便抛个异常”Runtime、自定 rollbackFor、避免异常被吞

所以别再张口闭口“数据库背锅,MySQL太坑”,其实各种事务悬案,80%是自己代码写崩的。


讲完了,收个尾巴: 踩坑不可怕,搞明白才是大佬。下一次,事务失效不要再冤枉你家数据库,先照照镜子想想,是不是自己三大要素没掌握。最后,用一句朋友总结的话:“你只要敢乱用@Transactional,坑就会如影随形。”

下班,溜了~