1、数据库的事务隔离级别和事务传播机制

【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道! 10w+人浏览 1.6k人参与

事务的隔离级别(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
方法不是 publicSpring 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(确认数据库支持)
  • 查询方法 → SUPPORTSNOT_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和死锁,才是王道!

如需具体场景的事务配置案例(如“用户注册+初始化账户+发欢迎邮件”),欢迎继续提问!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值