Spring中事务传播行为和隔离级别的组合方式

Posted by 开源圈 on July 6, 2025
---
layout:     post
title:      Spring中事务的传播行为和隔级别如何组合使用?
subtitle:   system design problems
date:       2025-06-17
author:     wuhua
header-img: img/post-bg-cook.jpg
catalog: true
tags:
    - Isral
---

在 Spring 事务管理中,传播行为 (Propagation Behavior)隔离级别 (Isolation Level) 是两个独立但可以组合使用的核心概念。它们分别解决了不同维度的事务控制问题:

  1. 传播行为 (Propagation Behavior): 定义了一个事务方法被另一个事务方法调用时,事务应该如何传播。它解决了事务的作用域和边界问题(例如,是加入现有事务,还是开启一个新事务,还是非事务执行)。
  2. 隔离级别 (Isolation Level): 定义了多个并发事务同时访问和修改相同数据时,一个事务能看到其他事务未提交或已提交数据的程度。它解决了事务之间的可见性问题,旨在防止脏读、不可重复读、幻读等并发问题。

组合方式

关键点:传播行为和隔离级别是正交的,理论上可以任意组合使用。 没有固定的“官方组合列表”,因为选择哪种组合完全取决于你的具体业务场景和并发控制需求。

传播行为 (7种)

  1. REQUIRED (默认): 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。
  2. SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
  3. MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  4. REQUIRES_NEW: 创建一个新事务,并挂起当前事务(如果存在)。新事务独立提交或回滚,不受外部事务影响。
  5. NOT_SUPPORTED: 以非事务方式执行操作,并挂起当前事务(如果存在)。
  6. NEVER: 以非事务方式执行,如果当前存在事务,则抛出异常。
  7. NESTED: 如果当前存在事务,则在当前事务内创建一个嵌套事务(保存点)。嵌套事务可以独立于外部事务进行回滚。如果当前没有事务,其行为与 REQUIRED 相同。

隔离级别 (5种)

  1. DEFAULT (默认): 使用底层数据库的默认隔离级别。对于大多数数据库(如 MySQL, PostgreSQL),通常是 READ_COMMITTED
  2. READ_UNCOMMITTED: 最低的隔离级别。允许读取其他事务尚未提交的更改(脏读)。可能导致脏读、不可重复读和幻读。
  3. READ_COMMITTED: 保证一个事务只能读取到其他事务已提交的数据。可防止脏读,但可能出现不可重复读和幻读。这是许多数据库的默认级别。
  4. REPEATABLE_READ: 保证在同一个事务中多次读取同一数据的结果是一致的(即使其他事务修改并提交了该数据)。可防止脏读和不可重复读,但可能出现幻读(MySQL InnoDB 通过 MVCC 机制在这个级别也避免了幻读)。
  5. SERIALIZABLE: 最高的隔离级别。所有事务按顺序依次执行。完全防止脏读、不可重复读和幻读,但性能开销最大,并发性最低。

如何组合与选择?

你可以为任何使用了 @Transactional 注解的方法(或类)同时指定 propagationisolation 属性。

@Service
public class MyService {

    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
    public void performCriticalUpdate() {
        // 这个方法总是在一个新的事务中执行,该事务的隔离级别是 REPEATABLE_READ
        // 外部事务(如果存在)会被挂起
        // ...
    }

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED) // 显式声明默认值
    public void standardOperation() {
        // 这个方法加入现有事务或创建新事务(REQUIRED),使用 READ_COMMITTED 隔离级别
        // ...
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void nonTransactionalOperation() {
        // 这个方法以非事务方式执行,不关心隔离级别(因为没事务)
        // ...
    }
}

组合时的注意事项

  1. 传播行为主导事务边界: 传播行为决定了方法执行时是否有事务、是新事务还是加入现有事务。隔离级别只在事务存在时才生效。对于 NOT_SUPPORTED, NEVER, SUPPORTS(当没有当前事务时) 这些传播行为,方法内的操作是在非事务上下文中执行的,指定的隔离级别将被忽略。
  2. 新事务与隔离级别: 当传播行为导致创建新事务时(如 REQUIRED 在无事务时、REQUIRES_NEW, NESTED),新事务会使用该方法指定的隔离级别(如果未指定,则使用默认值,通常是 DEFAULT -> 数据库默认)。
  3. 加入现有事务与隔离级别: 当传播行为导致加入现有事务时(如 REQUIRED, SUPPORTS, MANDATORY 在有事务时),该方法指定的隔离级别通常会被忽略。它会沿用现有事务的隔离级别。这是为了保持整个事务的一致性。你不能在一个已经运行的事务中途改变其隔离级别。
  4. 嵌套事务 (NESTED): NESTED 在现有事务内创建的是一个保存点。虽然它有自己的“回滚点”,但它仍然运行在外部事务的隔离级别下。你不能为嵌套事务指定一个不同于外部事务的隔离级别。隔离级别作用于整个外部事务。
  5. 性能与正确性的权衡: 隔离级别越高(如 SERIALIZABLE),并发控制越严格,数据一致性越好,但性能开销(锁竞争、回滚段管理)也越大。传播行为如 REQUIRES_NEW 能提供独立的事务边界,但频繁创建新事务也有开销。选择组合时需要根据业务逻辑对数据一致性的要求和对性能的敏感度进行平衡。
  6. 数据库支持: 并非所有数据库都支持所有的隔离级别或传播行为(尤其是 NESTED)。例如,Oracle 不支持 REPEATABLE_READ,JDBC 规范也不要求支持 NESTED。在使用前需确认你的数据库和 JDBC 驱动支持所选的组合。

常见的组合场景示例

  1. REQUIRED + READ_COMMITTED (最常见默认组合): 适用于大多数业务逻辑,平衡了数据一致性和性能。
  2. REQUIRES_NEW + READ_COMMITTED/REPEATABLE_READ:
    • 场景: 记录审计日志、发送通知消息。即使主业务逻辑失败回滚,这些辅助操作也需要成功提交。
    • 说明: 使用 REQUIRES_NEW 确保日志/消息独立提交。隔离级别根据日志读取需求选择,通常 READ_COMMITTED 足够。
  3. REQUIRED + REPEATABLE_READ/SERIALIZABLE:
    • 场景: 对数据一致性要求极高的核心操作,如金融交易、库存扣减(防止超卖)。
    • 说明: 提高隔离级别防止不可重复读或幻读对业务逻辑造成的干扰。
  4. SUPPORTS + DEFAULT:
    • 场景: 查询方法。如果有事务(例如在写操作中调用查询),则加入事务保证查询到最新提交的数据(READ_COMMITTED);如果没有事务,则直接执行简单查询。
  5. NOT_SUPPORTED + (忽略):
    • 场景: 执行一些与事务无关、或必须绕过事务的耗时操作(如文件 I/O、调用外部非事务 API)。
    • 说明: 挂起当前事务(如果存在),避免长事务持有锁,提高并发性。隔离级别无意义。
  6. NESTED + REPEATABLE_READ (理想配合):
    • 场景: 一个复杂操作中的子操作,该子操作可能失败但不希望导致整个大操作回滚(只需回滚到子操作前的保存点)。
    • 说明: REPEATABLE_READSERIALIZABLE 能更好地保证在嵌套事务回滚点之后,外部事务再次读取数据时的一致性(避免读到嵌套事务内已回滚的中间状态)。但需注意数据库支持情况(如 MySQL InnoDB 支持 NESTED)。

总结

Spring 事务的传播特性和隔离级别提供了灵活且强大的并发控制手段。它们可以自由组合使用,没有预定义的固定组合列表。选择哪种组合取决于:

  • 业务逻辑的事务边界需求 (传播行为): 操作是否需要事务?需要新事务还是加入现有事务?
  • 业务逻辑的数据一致性需求 (隔离级别): 操作对脏读、不可重复读、幻读的容忍度如何?
  • 性能考量: 更高的隔离级别和创建新事务 (REQUIRES_NEW) 通常带来更大的开销。
  • 数据库支持: 确认选用的组合在你的数据库和 JDBC 驱动上有效。

理解每个传播行为和隔离级别的含义及其组合时的相互作用(特别是关于新事务创建和加入现有事务时隔离级别的处理规则),是正确配置 Spring 事务的关键。务必根据具体业务场景仔细权衡选择。