事务的隔离级别(Isolation Level) 和 传播机制(Propagation Behavior) 是 Java 后端开发中处理数据库事务的两个核心概念。它们虽然都与“事务”有关,但解决的问题完全不同:
✅ 一、事务隔离级别(Isolation Level)
📌 1. 什么是事务隔离级别?
事务隔离级别是数据库层面的概念,用于控制多个并发事务之间的可见性和影响程度,主要解决的是“并发读写”带来的数据一致性问题。
💡 简单说:它决定“一个事务能不能看到另一个事务未提交/已提交的数据”。
📌 2. 四种标准隔离级别(SQL-92标准)
| 隔离级别 | 脏读(Dirty Read) | 不可重复读(Non-repeatable Read) | 幻读(Phantom Read) | 加锁策略 |
|---|---|---|---|---|
| READ UNCOMMITTED | ✅ 允许 | ✅ 允许 | ✅ 允许 | 无锁或极少锁 |
| READ COMMITTED | ❌ 禁止 | ✅ 允许 | ✅ 允许 | 读已提交数据加锁 |
| REPEATABLE READ | ❌ 禁止 | ❌ 禁止 | ✅ 允许(MySQL默认解决) | 行锁 + Gap Lock(MySQL) |
| SERIALIZABLE | ❌ 禁止 | ❌ 禁止 | ❌ 禁止 | 表锁或范围锁,串行执行 |
⚠️ 注意:不同数据库对隔离级别的实现略有差异。比如 MySQL 的
REPEATABLE READ默认通过 MVCC + Next-Key Lock 解决了幻读问题。
📌 3. 三大并发问题详解
(1)脏读(Dirty Read)
事务A读取了事务B未提交的数据,事务B回滚 → 事务A读到了“不存在”的数据。
✅ 示例:
事务B:UPDATE account SET balance = 100 WHERE id = 1; (未提交)
事务A:SELECT balance FROM account WHERE id = 1; → 读到100
事务B:ROLLBACK;
→ 事务A读到的数据是“脏数据”
(2)不可重复读(Non-repeatable Read)
同一个事务内,两次读取同一行数据,结果不一致 → 因为其他事务提交了修改。
✅ 示例:
事务A:SELECT balance FROM account WHERE id = 1; → 100
事务B:UPDATE account SET balance = 200 WHERE id = 1; COMMIT;
事务A:SELECT balance FROM account WHERE id = 1; → 200 (变了!)
(3)幻读(Phantom Read)
同一个事务内,两次执行相同的范围查询,结果集行数不同 → 因为其他事务插入/删除了数据。
✅ 示例:
事务A:SELECT * FROM orders WHERE user_id = 100; → 返回3条
事务B:INSERT INTO orders (user_id) VALUES (100); COMMIT;
事务A:SELECT * FROM orders WHERE user_id = 100; → 返回4条(出现“幻影”行)
🧠 记忆口诀:
- 脏读 → 读到别人“没提交”的数据
- 不可重复读 → 同一行数据前后读不一致
- 幻读 → 同一个查询,多/少了几行
📌 4. 如何设置隔离级别?
✅ 在数据库中设置(MySQL 示例):
-- 查看当前会话隔离级别
SELECT @@transaction_isolation;
-- 设置当前会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
✅ 在 Spring 中设置(推荐):
@Transactional(isolation = Isolation.READ_COMMITTED)
public void transferMoney() {
// ...
}
⚠️ 注意:Spring 设置的隔离级别最终要数据库支持,否则可能被忽略或报错。
✅ 二、事务传播机制(Propagation Behavior)
📌 1. 什么是事务传播机制?
事务传播机制是 Spring 框架层面的概念,用于控制一个带有 @Transactional 的方法被另一个事务方法调用时,事务应该如何“传播”或“行为”。
💡 简单说:它决定“方法A调用方法B时,B用哪个事务?是挂起?加入?还是新建?”
📌 2. 七种传播行为(Spring 定义)
| 传播行为 | 含义 | 适用场景 |
|---|---|---|
REQUIRED(默认) | 如果当前存在事务,则加入;否则新建 | 最常用,适用于大多数业务方法 |
SUPPORTS | 如果当前存在事务,则加入;否则以非事务方式执行 | 查询类方法,不强制要求事务 |
MANDATORY | 必须在事务中运行,否则抛异常 | 强一致性操作,如扣库存 |
REQUIRES_NEW | 总是新建事务,挂起当前事务(如果存在) | 日志记录、发消息,需独立提交 |
NOT_SUPPORTED | 以非事务方式执行,挂起当前事务 | 调用外部服务、耗时操作 |
NEVER | 不能在事务中运行,否则抛异常 | 明确禁止事务的场景 |
NESTED | 如果当前存在事务,则嵌套执行(保存点);否则等同 REQUIRED | 部分失败可回滚,整体可继续 |
📌 3. 重点传播行为详解
✅ REQUIRED(默认)
@Transactional
public void methodA() {
methodB(); // methodB 也 @Transactional → 加入 methodA 的事务
}
@Transactional
public void methodB() { ... }
→ A 和 B 在同一个事务中,B 出错 → A 也会回滚。
✅ REQUIRES_NEW(独立事务)
@Transactional
public void methodA() {
methodB(); // methodB 新开事务,A 挂起
// 即使 B 提交了,A 回滚也不会影响 B
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() { ... }
→ 常用于:记录操作日志、发送通知,即使主事务失败,日志也要保留。
⚠️ 注意:REQUIRES_NEW 要求被调用方法必须是 代理对象调用(不能 this.methodB(),必须通过 Spring 注入的对象调用)。
✅ NESTED(嵌套事务)
@Transactional
public void methodA() {
try {
methodB(); // 嵌套事务,设置保存点
} catch (Exception e) {
// 可回滚到保存点,不影响后续操作
}
methodC();
}
@Transactional(propagation = Propagation.NESTED)
public void methodB() { ... }
→ 支持部分回滚,但数据库必须支持保存点(Savepoint),如 MySQL InnoDB。
✅ 三、隔离级别 vs 传播机制 —— 区别总结
| 维度 | 隔离级别(Isolation) | 传播机制(Propagation) |
|---|---|---|
| 所属层面 | 数据库层面 | Spring 框架层面 |
| 解决问题 | 并发事务之间的数据可见性 | 方法调用时事务的创建/加入/挂起行为 |
| 配置位置 | 数据库 / @Transactional(isolation=…) | @Transactional(propagation=…) |
| 影响范围 | 所有并发事务的交互行为 | 方法调用链中的事务上下文传递 |
| 是否依赖数据库 | 是(不同数据库实现不同) | 否(Spring 控制,但部分行为如 NESTED 依赖数据库) |
🧩 一句话总结:
- 隔离级别 → 控制“别人改的数据我能不能看到”
- 传播机制 → 控制“我这个方法要不要新开事务、加入现有事务”
✅ 四、实际开发中的注意事项
⚠️ 1. 隔离级别注意事项
- 不要盲目使用 SERIALIZABLE:虽然最安全,但性能极差,容易锁表。
- MySQL 默认 REPEATABLE READ,已解决幻读(Next-Key Lock),适合大多数场景。
- READ COMMITTED 更适合高并发 OLTP 系统(如订单、支付),避免长事务锁住数据。
- 修改隔离级别前,务必测试并发场景,避免死锁或性能下降。
⚠️ 2. 传播机制注意事项
✅ 必须通过代理调用
@Service
public class OrderService {
@Autowired
private OrderService self; // 自己注入自己(获取代理对象)
@Transactional
public void createOrder() {
// ❌ 错误:this.saveLog() → 不走代理,事务传播失效!
// ✅ 正确:self.saveLog() → 走代理,传播行为生效
self.saveLog();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog() { ... }
}
🔧 替代方案:使用
AopContext.currentProxy()(需开启 expose-proxy)
✅ REQUIRES_NEW 的异常处理
@Transactional
public void methodA() {
try {
methodB(); // REQUIRES_NEW,独立提交
} catch (Exception e) {
// 如果这里捕获异常,A 不回滚,B 已提交 → 数据可能不一致!
log.error("methodB failed but already committed!");
}
}
→ 如果 B 是扣库存,A 是下单,B 成功提交后 A 失败 → 库存扣了但订单没生成 → 数据不一致!
✅ 解决方案:
- 不要轻易捕获 REQUIRES_NEW 方法的异常
- 或者改用 NESTED(支持回滚到保存点)
✅ NESTED 的数据库支持
- MySQL InnoDB ✅ 支持 Savepoint
- Oracle ✅ 支持
- 某些数据库或驱动 ❌ 不支持 → 会退化为 REQUIRED
⚠️ 3. 事务失效常见场景(重点!)
| 场景 | 原因 | 解决方案 |
|---|---|---|
| this 调用带 @Transactional 的方法 | 没有经过 Spring 代理 | 注入自己 / 使用 AopContext |
| 方法不是 public | Spring AOP 默认只代理 public 方法 | 改为 public |
| 异常被捕获未抛出 | Spring 默认只对 RuntimeException 回滚 | @Transactional(rollbackFor = Exception.class) |
| 数据库不支持事务(如 MyISAM) | 引擎问题 | 改用 InnoDB |
| 传播行为配置错误(如 NOT_SUPPORTED 中写数据) | 无事务保护 | 检查传播行为是否合理 |
✅ 五、最佳实践建议
🎯 1. 隔离级别选择建议
- 90% 场景使用数据库默认(MySQL → REPEATABLE READ)
- 高并发更新场景 → 改为 READ COMMITTED(减少锁竞争)
- 报表/统计类 → 可用 READ UNCOMMITTED(允许脏读,换性能)
🎯 2. 传播机制选择建议
- 默认用
REQUIRED - 独立提交操作(日志、消息)→
REQUIRES_NEW - 需部分回滚 →
NESTED(确认数据库支持) - 查询方法 →
SUPPORTS或NOT_SUPPORTED
🎯 3. 事务方法设计原则
- 保持事务短小(避免长事务锁表)
- 避免在事务中调用远程服务、发邮件、上传文件
- 事务方法中不要捕获异常“吃掉”(除非明确处理)
- 使用
@Transactional(timeout = 5)设置超时,避免死锁
✅ 六、实战案例:电商下单场景
@Service
public class OrderService {
@Autowired
private OrderService self;
@Transactional
public void createOrder(Order order) {
// 1. 扣库存(要求强一致)
reduceStock(order);
// 2. 创建订单
saveOrder(order);
// 3. 记录日志(即使下单失败,日志也要保留)
self.logOrderAction(order.getId(), "ORDER_CREATED");
// 4. 发送消息(独立事务)
self.sendMessage("OrderCreated", order.getId());
}
@Transactional(propagation = Propagation.MANDATORY)
public void reduceStock(Order order) {
// 必须在事务中运行,否则抛异常
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOrderAction(Long orderId, String action) {
// 独立提交,不受主事务影响
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendMessage(String event, Long orderId) {
// 发送MQ消息,独立事务
}
}
✅ 总结
| 概念 | 作用 | 关键点 |
|---|---|---|
| 隔离级别 | 控制并发事务间的数据可见性 | 脏读、不可重复读、幻读;数据库支持是关键 |
| 传播机制 | 控制方法调用时事务的创建与传播行为 | REQUIRED(默认)、REQUIRES_NEW(独立)、NESTED(嵌套)最常用 |
| 区别 | 一个管“数据能不能看”,一个管“事务怎么传” | 层面不同,互不替代,需配合使用 |
| 注意事项 | 代理调用、异常处理、数据库支持、事务范围 | 避免事务失效,保持短事务,合理选择级别和传播行为 |
📌 最后提醒:事务不是万能的,设计不当反而会导致性能瓶颈或数据不一致。理解原理 + 多测试 + 监控慢SQL和死锁,才是王道!
如需具体场景的事务配置案例(如“用户注册+初始化账户+发欢迎邮件”),欢迎继续提问!
996

被折叠的 条评论
为什么被折叠?



