为什么 @Transactional 会失效?
面试频率:★★★★★
工作频率:★★★★★
⚡30 秒速记(复习只看这里)
🟢 一句话
Spring 事务本质依赖 Spring AOP Proxy,只有经过代理对象调用,事务才会生效。
🟡 五大高频原因(★★★★★)
- 同类内部调用(this.xxx())
- private 方法
- final 方法
- 异常被 catch
- rollbackFor 未配置
🟠 REQUIRES_NEW
- 挂起外层事务
- 开启独立事务
- 内外事务互不影响
- 常用于日志、审计、失败记录
🔴 一句话
事务本质是 AOP Proxy,不经过代理对象,事务不会生效。
📚 完整知识
一、事务底层原理
Spring 并不是因为 @Transactional 注解才有事务。
真正执行流程:
Controller
│
▼
Spring Proxy(代理对象)
│
▼
TransactionInterceptor
│
▼
PlatformTransactionManager
│
▼
开启事务
│
▼
执行目标方法
│
├── 成功 → Commit
└── 异常 → Rollback
真正负责事务的是:
TransactionInterceptor
不是注解。
二、为什么会失效?
① 同类内部调用(★★★★★)
错误
@Service
public class OrderService {
public void createOrder() {
saveOrder();
}
@Transactional
public void saveOrder() {
}
}
实际上执行的是:
this.saveOrder();
没有经过 Spring Proxy。
事务不会开启。
正确
@Service
public class OrderService {
@Autowired
private OrderTxService orderTxService;
public void createOrder() {
orderTxService.saveOrder();
}
}
② private 方法(★★★★★)
@Transactional
private void save() {
}
Spring 默认不会代理 private 方法。
事务失效。
③ final 方法(★★★★☆)
@Transactional
public final void save() {
}
CGLIB 需要重写方法。
final 无法重写。
事务失效。
④ 异常被 catch(★★★★★)
错误:
@Transactional
public void save() {
try {
int i = 1 / 0;
} catch (Exception e) {
log.error("error");
}
}
Spring认为:
方法正常结束。
最终:
Commit
正确:
catch (Exception e) {
throw e;
}
或者:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
⑤ rollbackFor 未配置(★★★★★)
默认只回滚:
RuntimeException
Error
例如:
throw new Exception();
不会回滚。
正确:
@Transactional(
rollbackFor = Exception.class
)
三、REQUIRES_NEW
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
表示:
挂起外层事务,开启新的独立事务。
例如:
外层事务
↓
保存订单
↓
调用 saveLog()
↓
REQUIRES_NEW
↓
日志提交成功
↓
恢复外层事务
↓
订单异常
↓
订单回滚
最终:
订单 ❌
日志 ✅
适用:
- 操作日志
- 审计日志
- 消息记录
- 失败记录
一般不要用于订单、库存等核心业务。
💼 工作场景
场景一:本地事务
保存订单
↓
扣库存
↓
更新积分
如果:
更新积分失败
最终:
订单回滚
库存回滚
积分回滚
属于:
本地事务
场景二:数据库 + MQ
保存订单
↓
扣库存
↓
发送 MQ
↓
更新积分
如果:
MQ 已发送成功
↓
更新积分失败
↓
数据库回滚
最终可能:
数据库没有订单
MQ 已消费
普通 @Transactional
无法保证:
数据库 + MQ 原子性
通常需要:
- 本地消息表
- RocketMQ 事务消息
- 最终一致性方案
场景三:微服务
订单服务
↓
库存服务
↓
积分服务
即使:
@Transactional
也只能保证:
订单数据库
不能保证:
库存服务
积分服务
一起回滚。
属于:
分布式事务
🎤 面试回答(30 秒)
Spring 的事务底层是通过 AOP 代理实现的,而不是 @Transactional 注解本身。只有方法经过 Spring 创建的代理对象调用时,事务才会生效。如果同类内部调用、private 方法、final 方法、异常被 catch 没有继续抛出,或者抛出的异常不是默认回滚类型,都可能导致事务失效。另外,REQUIRES_NEW 会开启一个新的独立事务,即使外层事务回滚,内层事务也不会回滚,因此一般用于日志、审计等需要独立提交的场景。
🎯 面试追问
Q1:为什么 this 调用会失效?
答:
没有经过 Spring Proxy。
没有执行 TransactionInterceptor。
Q2:为什么事务依赖代理?
答:
Spring 使用 AOP 增强目标方法。
真正开启事务的是:
TransactionInterceptor
Q3:为什么 final 不行?
答:
CGLIB 通过继承重写方法实现增强。
final 无法重写。
Q4:数据库 + MQ 为什么不能只靠 @Transactional?
答:
@Transactional 只能保证本地数据库事务。
MQ 属于另外一种资源。
需要事务消息、本地消息表等方案保证最终一致性。
⚠️ 工作踩坑
✅ this.xxx()
事务失效
✅ catch 后没有继续抛异常
事务提交
✅ checked Exception
没有 rollbackFor
事务提交
✅ 滥用 REQUIRES_NEW
部分数据提交
部分数据回滚
❓ 自测(复习时只看这里)
- 为什么 @Transactional 会失效?
- Spring 事务底层是谁实现的?
- 为什么 this.xxx() 不生效?
- rollbackFor 默认回滚哪些异常?
- REQUIRES_NEW 是什么?
- 数据库 + MQ 为什么属于一致性问题?
- 什么情况下属于分布式事务?
Spring 事务 Commit / Rollback 原理 ⭐⭐⭐⭐⭐
面试频率:★★★★☆
核心理解:Spring 负责管理事务,真正的数据提交和回滚由数据库完成。
⚡30 秒速记
@Transactional
↓
Spring AOP代理
↓
事务拦截器
↓
开启数据库事务
↓
执行SQL
↓
方法正常?
↙ ↘
是 否
↓ ↓
commit 判断是否需要rollback
最重要的一句话:
Spring 不会自己把 SQL 反着执行,Spring 负责控制 commit / rollback,真正的数据恢复由数据库事务机制完成。
MySQL InnoDB 中:
undo log
= 回滚 + MVCC历史版本的重要基础
redo log
= 崩溃恢复的重要基础
一、@Transactional 到底做了什么?
例如:
@Transactional
public void createOrder() {
insertOrder();
updateStock();
}
看起来:
调用createOrder()
↓
执行SQL
实际上 Spring 还帮我们管理了事务。
大致流程:
调用者
↓
Spring代理对象
↓
事务拦截器
↓
开启事务
↓
执行createOrder()
↓
insertOrder()
↓
updateStock()
↓
方法正常结束
↓
commit
所以:
@Transactional
本身只是事务配置。
真正处理事务的是:
Spring事务代理
+
事务拦截器
+
事务管理器
二、正常情况下怎么提交?
例如:
@Transactional
public void createOrder() {
insertOrder();
updateStock();
}
可以粗略理解:
Spring开启事务
↓
获得数据库Connection
↓
关闭自动提交
autoCommit = false
↓
执行INSERT
↓
执行UPDATE
↓
方法正常结束
↓
commit()
最终:
事务提交
数据库中的修改正式完成。
三、发生异常怎么办? ⭐⭐⭐⭐⭐
例如:
@Transactional
public void createOrder() {
insertOrder();
updateStock();
throw new RuntimeException();
}
执行:
Spring开启事务
↓
INSERT
↓
UPDATE
↓
RuntimeException
↓
异常抛给事务拦截器
↓
判断是否满足回滚规则
↓
满足
↓
rollback()
最终:
INSERT
+
UPDATE
↓
一起回滚
四、Spring 是怎么把数据恢复的?
很多人第一次会认为:
INSERT
↓
发生异常
↓
Spring执行DELETE
或者:
UPDATE 1000 → 500
↓
发生异常
↓
Spring再执行UPDATE 500 → 1000
不是。
Spring:
不会自己把 SQL 反着执行一遍。
Spring主要负责:
告诉数据库:
这个事务需要rollback
真正恢复数据的是:
数据库
五、Spring 和数据库分别负责什么? ⭐⭐⭐⭐⭐
Spring
负责:
什么时候开启事务
↓
哪些方法加入事务
↓
事务传播
↓
什么时候commit
↓
什么时候rollback
例如:
@Transactional
属于 Spring 的事务管理。
数据库
负责:
真正执行事务
↓
数据修改
↓
事务隔离
↓
锁
↓
commit
↓
rollback
↓
日志 / MVCC 等底层机制
所以可以简单记:
Spring
=
事务管理员
数据库
=
真正执行事务的人
六、MySQL 为什么能 rollback?
如果使用:
MySQL InnoDB
这里就涉及:
undo log
中文:
回滚日志。
例如:
原来:
张三余额 = 1000
执行:
UPDATE account
SET balance = 500
WHERE name = '张三';
事务还没有提交。
InnoDB 会维护用于恢复旧数据的信息。
可以粗略理解:
原数据:
balance = 1000
↓
记录旧版本相关信息
↓
修改数据
↓
balance = 500
如果:
COMMIT
事务正常提交。
如果:
ROLLBACK
数据库可以根据 undo 信息:
500
↓
恢复
↓
1000
七、undo log 是什么? ⭐⭐⭐⭐⭐
undo:
撤销
所以:
undo log
=
撤销日志 / 回滚日志
主要作用之一:
事务回滚
例如:
1000
↓
修改
↓
500
undo 中保留:
恢复旧数据所需要的信息
发生:
rollback
就可以:
500
↓
恢复
↓
1000
八、undo log 不只是用来回滚
undo log 还有一个非常重要的作用:
MVCC
你前面事务隔离学过:
REPEATABLE READ
↓
MVCC
↓
历史版本
undo log:
和这些历史版本密切相关。
可以先简单理解:
undo log
/ \
/ \
rollback MVCC
↓ ↓
恢复数据 历史版本
所以:
undo log 既是事务回滚的重要基础,也和 MVCC 的历史版本读取密切相关。
九、举个 MVCC 的简单例子
原来:
张三余额 = 1000
事务B:
修改:
1000 → 500
数据库中:
会维护相关版本信息。
事务A:
根据:
MVCC
+
Read View
可能仍然读取:
1000
其他满足条件的事务:
可能读取:
500
所以:
undo log
↓
历史版本
↓
MVCC
↓
不同事务读取合适版本
注意:
这里先理解概念。
具体:
版本链
trx_id
roll_pointer
Read View
后面学 MySQL MVCC 再详细讲。
十、redo log 又是什么?
除了:
undo log
MySQL InnoDB 还有:
redo log
redo:
重新做
它主要和:
崩溃恢复、持久性
有关。
例如:
事务已经提交。
但是突然:
服务器断电
MySQL 重启以后:
可以利用 redo log 等机制:
恢复已经提交但尚未完整落盘的数据修改
十一、undo 和 redo 怎么记? ⭐⭐⭐⭐⭐
最简单:
undo
=
后悔药
用于:
我要反悔
↓
rollback
redo
=
备忘录
用于:
机器突然挂了
↓
重启
↓
恢复应该持久化的数据
所以:
undo
↓
回滚
redo
↓
崩溃恢复
这是目前阶段最重要的区别。
十二、Spring rollback 完整流程 ⭐⭐⭐⭐⭐
例如:
@Transactional(
rollbackFor = Exception.class
)
public void createOrder() throws Exception {
insertOrder();
updateStock();
throw new Exception();
}
完整理解:
调用createOrder()
↓
Spring代理对象
↓
事务拦截器
↓
开启数据库事务
↓
执行INSERT
↓
执行UPDATE
↓
发生Exception
↓
异常向外抛出
↓
事务拦截器收到异常
↓
检查rollbackFor
↓
Exception符合回滚规则
↓
Spring事务管理器执行rollback
↓
数据库收到rollback
↓
InnoDB利用自身事务机制
↓
撤销事务中的修改
十三、为什么 catch 异常会影响 rollback?
之前学事务失效的时候:
@Transactional
public void createOrder() {
try {
insertOrder();
throw new RuntimeException();
} catch (Exception e) {
log.error("失败", e);
}
}
现在可以从底层流程理解。
事务代理:
开启事务
↓
调用createOrder()
↓
方法正常返回
因为:
异常已经被catch掉
所以事务拦截器:
没有收到异常
于是可能:
commit
如果:
catch (Exception e) {
log.error("失败", e);
throw e;
}
流程:
发生异常
↓
catch
↓
重新throw
↓
异常传给事务拦截器
↓
判断回滚规则
↓
rollback
所以:
rollback 的关键之一,是事务拦截器能够感知到满足回滚规则的异常。
十四、rollbackFor 又是干什么的?
例如:
@Transactional(
rollbackFor = Exception.class
)
意思:
如果方法抛出的异常
属于Exception及其子类
↓
满足配置的回滚规则
事务拦截器:
执行rollback
所以:
rollbackFor
不是自己执行回滚
而是:
告诉 Spring 哪些异常应该被判断为需要回滚。
十五、Propagation 和 rollback 的关系
例如:
事务A
里面调用:
事务B
如果:
Propagation.REQUIRED
那么:
A
+
B
↓
同一个事务
发生需要回滚的异常:
可能导致:
整个事务回滚
如果:
Propagation.REQUIRES_NEW
那么:
事务A
↓
挂起
事务B
↓
独立事务
B:
commit
以后:
A再:
rollback
B已经独立提交的事务:
不会因为A后续回滚而自动一起回滚
因为:
A和B是两个事务
十六、Isolation 和 rollback 的关系
Isolation:
READ_COMMITTED
REPEATABLE_READ
SERIALIZABLE
主要解决:
多个事务并发时
↓
彼此的数据可见性
而 rollback:
解决:
当前事务失败
↓
撤销自己的修改
所以:
Isolation
=
事务之间怎么隔离
Rollback
=
事务失败怎么撤销
不要混淆。
十七、把 @Transactional 完整拆开 ⭐⭐⭐⭐⭐
现在看到:
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.REPEATABLE_READ,
rollbackFor = Exception.class
)
public void createOrder() {
}
你应该能解释:
@Transactional
Spring声明式事务
底层:
AOP代理
propagation = REQUIRED
事务怎么传播
规则:
有事务 → 加入
没事务 → 创建
isolation = REPEATABLE_READ
事务之间怎么隔离
主要解决:
并发事务的数据可见性问题
rollbackFor = Exception.class
哪些异常应该触发回滚判断
最终:
发生符合规则的异常
↓
Spring事务拦截器
↓
rollback
↓
数据库
↓
真正撤销事务修改
十八、Spring事务完整知识链 ⭐⭐⭐⭐⭐
@Transactional
↓
Spring AOP
↓
动态代理
↓
事务拦截器
↓
事务管理器
↓
数据库事务
事务怎么传:
Propagation
事务怎么隔离:
Isolation
什么异常回滚:
rollbackFor
事务失败:
Spring
↓
rollback
↓
数据库
↓
撤销修改
MySQL底层:
undo log
↓
回滚 + MVCC历史版本
redo log
↓
崩溃恢复
十九、Spring 和 MySQL 的边界 ⭐⭐⭐⭐⭐
这个一定要理解。
Spring:
@Transactional
AOP
事务传播
事务管理器
commit / rollback控制
属于:
Spring
而:
undo log
redo log
MVCC
Read View
版本链
锁
属于:
MySQL / InnoDB
所以:
Spring负责管理事务
数据库负责真正实现事务
🎤 面试回答(30秒)
Spring 的 @Transactional 主要基于 AOP 代理实现。调用事务方法时,Spring 的事务拦截器会通过事务管理器开启事务。
如果方法正常执行完成,就提交事务;如果方法抛出了符合回滚规则的异常,就执行 rollback。
Spring 本身不会把之前执行的 SQL 反向执行,而是通知数据库回滚。真正的数据恢复由数据库事务机制完成。
例如 MySQL InnoDB 中,undo log 是事务回滚和 MVCC 历史版本的重要基础之一,而 redo log 主要用于崩溃恢复和保证持久性。
🎯 面试追问
Q1:@Transactional 发生异常后是谁负责回滚数据?
答:
Spring
↓
决定是否rollback
↓
调用事务管理器
数据库
↓
真正完成数据回滚
Q2:Spring 会把 SQL 反着执行吗?
答:
不会。
例如:
INSERT
失败以后:
Spring不会自己:
DELETE
而是:
Spring调用rollback
↓
数据库事务机制负责恢复
Q3:undo log 是什么?
答:
MySQL InnoDB 中用于事务回滚和维护 MVCC 历史版本的重要机制之一。
Q4:redo log 是什么?
答:
InnoDB 用于崩溃恢复、保证事务持久性的重要日志。
Q5:undo 和 redo 最大区别?
答:
undo
↓
回滚 / 历史版本
redo
↓
崩溃恢复
Q6:rollbackFor 是自己负责回滚吗?
答:
不是。
它主要:
告诉Spring
哪些异常满足回滚规则
真正执行:
rollback
的是事务管理机制。
Q7:为什么 catch 异常以后可能不回滚?
答:
因为:
异常被catch
↓
没有继续抛出
↓
事务拦截器没有感知到异常
↓
可能正常commit
Q8:REQUIRES_NEW 内层事务提交以后,外层回滚会把它回滚吗?
答:
不会自动一起回滚。
因为:
外层 → 事务A
内层 → 独立事务B
B已经独立提交。
Q9:Spring 和数据库在事务中分别负责什么?
答:
Spring:
事务管理
传播行为
回滚规则
commit / rollback控制
数据库:
真正的数据事务
隔离
锁
回滚
日志
MVCC
❓ 自测
-
@Transactional 底层为什么能控制事务?
-
Spring 事务正常结束后会执行什么?
-
发生符合回滚规则的异常后会执行什么?
-
Spring 会自己把 SQL 反着执行吗?
-
真正负责恢复数据的是 Spring 还是数据库?
-
undo log 是什么?
-
undo log 除了回滚,还和什么机制有关?
-
redo log 是什么?
-
undo log 和 redo log 最大区别是什么?
-
为什么 catch 异常可能导致事务没有回滚?
-
rollbackFor = Exception.class 是什么意思?
-
rollbackFor 自己会执行 rollback 吗?
-
REQUIRED 和 rollback 有什么关系?
-
REQUIRES_NEW 已经提交后,外层事务回滚会自动把它一起回滚吗?
-
Isolation 和 rollback 分别解决什么问题?
-
Spring 和 MySQL 在事务中分别负责什么?