【Spring 事务】事务隔离级别与事务传播机制:从理论到业务落地实操

文章目录

    • 一、事务隔离级别​
      • [1.1 MySQL 事务隔离级别​](#1.1 MySQL 事务隔离级别)
      • [1.2 Spring 事务隔离级别​](#1.2 Spring 事务隔离级别)
    • [二、Spring 事务传播机制​](#二、Spring 事务传播机制)
      • [2.1 事务传播机制​概念](#2.1 事务传播机制概念)
      • [2.2 事务的传播机制分类](#2.2 事务的传播机制分类)
      • [2.3 Spring 事务传播机制使用](#2.3 Spring 事务传播机制使用)
        • [2.3.1 REQUIRED(加入事务)​](#2.3.1 REQUIRED(加入事务))
        • [2.3.2 REQUIRES_NEW(新建事务)​](#2.3.2 REQUIRES_NEW(新建事务))
        • [2.3.3 NEVER(不支持当前事务,抛异常)​](#2.3.3 NEVER(不支持当前事务,抛异常))
        • [2.3.4 NESTED(嵌套事务)​](#2.3.4 NESTED(嵌套事务))
      • [2.4 NESTED 和 REQUIRED 的区别​](#2.4 NESTED 和 REQUIRED 的区别)

一、事务隔离级别​

1.1 MySQL 事务隔离级别​

SQL 标准定义了四种隔离级别,MySQL 全都支持。这四种隔离级别分别是:​

  1. 读未提交(READ UNCOMMITTED) :读未提交,也叫未提交读。该隔离级别的事务可以看到其他事务中未提交的数据。

    因为其他事务未提交的数据可能会发生回滚,但该隔离级别却可以读到,这种级别下读到的数据被称为脏数据 ,对应的问题称为脏读

  2. 读提交(READ COMMITTED) :读已提交,也叫提交读。该隔离级别的事务能读取到已经提交事务的数据,不会出现脏读问题。

    但由于事务执行过程中可读取其他事务提交的结果,同一 SQL 查询在不同时间可能得到不同结果,这种现象称为不可重复读

  3. 可重复读(REPEATABLE READ) :事务不会读到其他事务对已有数据的修改(即使其他事务已提交),能确保同一事务多次查询结果一致。

    但其他事务新插入的数据可被感知,可能引发幻读 问题(例如:事务中多次查询同一条件数据,其他事务插入符合条件的新数据后,本事务查询结果行数不变,但插入时因唯一约束失败)。

    可重复读是 MySQL 的默认事务隔离级别。

  4. 串行化(SERIALIZABLE) :事务最高隔离级别,强制事务按顺序执行,避免冲突,可解决脏读、不可重复读和幻读问题。

    但执行效率极低,实际使用场景较少。

事务隔离级别与并发问题对应关系:

事务隔离级别 脏读 不可重复读 幻读
读未提交(READ UNCOMMITTED)
读提交(READ COMMITTED)
可重复读(REPEATABLE READ)
串行化(SERIALIZABLE)

1.2 Spring 事务隔离级别​

Spring 中事务隔离级别有 5 种,本质是对数据库隔离级别的抽象,通过 @Transactional 注解的 isolation 属性配置:

  1. Isolation.DEFAULT :以连接数据库的事务隔离级别为主(默认值),例如 MySQL 对应 REPEATABLE READ,Oracle 对应 READ COMMITTED
  2. Isolation.READ_UNCOMMITTED :对应 SQL 标准的 READ UNCOMMITTED,允许脏读、不可重复读、幻读。
  3. Isolation.READ_COMMITTED :对应 SQL 标准的 READ COMMITTED,禁止脏读,允许不可重复读、幻读。
  4. Isolation.REPEATABLE_READ :对应 SQL 标准的 REPEATABLE READ,禁止脏读、不可重复读,允许幻读。
  5. Isolation.SERIALIZABLE :对应 SQL 标准的 SERIALIZABLE,禁止所有并发问题,性能最低。

Spring 隔离级别枚举定义

Spring 隔离级别配置示例:通过 @Transactionalisolation 属性显式指定隔离级别。

java 复制代码
@RestController
@RequestMapping("/trans")
public class IsolationController {

    @Transactional(isolation = Isolation.READ_COMMITTED)
    @RequestMapping("/r3")
    public String r3(String name, String password) {
        // 业务逻辑(例如用户注册)
        // userService.registryUser(name, password);
        return "r3";
    }
}

二、Spring 事务传播机制​

2.1 事务传播机制​概念

事务传播机制是指:多个被 @Transactional 修饰的事务方法存在调用关系时,事务在方法间的传递规则。

例如:方法 A(含事务)调用方法 B(含事务),B 是加入 A 的事务,还是创建新事务?这一规则由事务传播机制决定。

  • 事务隔离级别:解决"多个事务同时操作同一数据库"的并发问题。
  • 事务传播机制:解决"一个事务在多个方法节点间传递"的协作问题。

2.2 事务的传播机制分类

Spring 事务传播机制通过 @Transactional 注解的 propagation 属性指定,共 7 种,对应 Propagation 枚举:

  1. Propagation.REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建新事务。

  2. Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式运行。

  3. Propagation.MANDATORY(强制性):如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

  4. Propagation.REQUIRES_NEW :创建新事务;如果当前存在事务,则将当前事务挂起。

    无论外部是否有事务,修饰的内部方法均开启独立事务,事务间互不干扰。

  5. Propagation.NOT_SUPPORTED:以非事务方式运行;如果当前存在事务,则将当前事务挂起。

  6. Propagation.NEVER:以非事务方式运行;如果当前存在事务,则抛出异常。

  7. Propagation.NESTED :如果当前存在事务,则创建"嵌套事务"(作为当前事务的子事务)运行;如果当前没有事务,则等价于 REQUIRED

Spring 传播机制枚举定义

2.3 Spring 事务传播机制使用

2.3.1 REQUIRED(加入事务)​

场景需求:用户注册(插入用户数据)+ 记录操作日志(插入日志数据),日志插入时出现异常,观察事务是否回滚。

代码实现

  1. Controller 层(开启事务):
java 复制代码
@RequestMapping("/propaga")
@RestController
public class PropagationController {
    @Autowired
    private UserService userService;
    @Autowired
    private LogService logService;

    // 事务传播机制:REQUIRED(默认)
    @Transactional(propagation = Propagation.REQUIRED)
    @RequestMapping("/p1")
    public String p1(String name, String password) {
        // 1. 用户注册(调用 UserService,加入当前事务)
        userService.registryUser(name, password);
        // 2. 记录日志(调用 LogService,加入当前事务,故意抛出异常)
        logService.insertLog(name, "用户注册");
        return "操作完成";
    }
}
  1. UserService 层(加入事务):
java 复制代码
@Slf4j
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public void registryUser(String name, String password) {
        // 插入用户数据
        userInfoMapper.insert(name, password);
        log.info("用户数据插入成功");
    }
}
  1. LogService 层(加入事务,故意抛出异常):
java 复制代码
@Slf4j
@Service
public class LogService {
    @Autowired
    private LogInfoMapper logInfoMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public void insertLog(String name, String op) {
        // 故意抛出算术异常
        int a = 10 / 0;
        // 记录日志(代码不会执行)
        logInfoMapper.insertLog(name, op);
    }
}

执行结果:数据库无任何数据插入。

流程描述

  1. Controller 的 p1 方法开启事务;
  2. userService.registryUser 加入 p1 的事务,用户数据插入成功;
  3. logService.insertLog 加入 p1 的事务,抛出异常导致事务回滚;
  4. 由于用户插入和日志插入属于同一事务,回滚后用户数据也被撤销。
2.3.2 REQUIRES_NEW(新建事务)​

场景需求:修改上述场景的传播机制为 REQUIRES_NEW,确保日志插入异常不影响用户注册。

代码修改

  1. UserService 层(新建事务):
java 复制代码
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void registryUser(String name, String password) {
    userInfoMapper.insert(name, password);
    log.info("用户数据插入成功");
}
  1. LogService 层(新建事务,故意抛出异常):
java 复制代码
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertLog(String name, String op) {
    int a = 10 / 0; // 异常
    logInfoMapper.insertLog(name, op);
}

执行结果:

  • 用户表:数据插入成功(UserService 的事务独立执行,无异常);
  • 日志表:数据插入失败(LogService 的事务抛出异常,独立回滚)。

核心结论:
REQUIRES_NEW 修饰的方法会开启独立事务,与外部事务互不干扰,适用于"不希望事务间相互影响"的场景(如:核心业务与日志记录分离)。

2.3.3 NEVER(不支持当前事务,抛异常)​

场景需求:修改UserService中对应的方法的事务传播机制为Propagation.NEVER

代码修改

java 复制代码
@Slf4j
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;

    // 传播机制:NEVER(禁止当前存在事务)
    @Transactional(propagation = Propagation.NEVER)
    public void registryUser(String name, String password) {
        userInfoMapper.insert(name, password);
    }
}

执行结果:程序报错,数据库无数据插入。异常信息示例:

2.3.4 NESTED(嵌套事务)​

场景需求:修改UserService和LogService中对应的方法的事务传播机制为 Propagation.NESTED

代码实现

  1. UserService 层(嵌套事务):
java 复制代码
@Transactional(propagation = Propagation.NESTED)
public void registryUser(String name, String password) {
    userInfoMapper.insert(name, password);
}
  1. LogService 层(嵌套事务,故意抛出异常):
java 复制代码
@Transactional(propagation = Propagation.NESTED)
public void insertLog(String name, String op) {
    int a = 10 / 0; // 异常
    logInfoMapper.insertLog(name, op);
}

执行结果:数据库无任何数据插入。

流程描述

  1. Controller 的 p1 方法开启父事务
  2. userService.registryUser 作为子事务(嵌套父事务),用户数据插入成功;
  3. logService.insertLog 作为子事务(嵌套父事务),抛出异常后回滚子事务;
  4. 由于子事务异常未处理,向上传递导致父事务回滚,用户数据也被撤销。

2.4 NESTED 和 REQUIRED 的区别​

核心差异:是否支持"部分回滚"

区别:

  • 事务全部执行成功时,二者的结果一致。
  • 事务部分执行成功时:REQUIRED加入事务会导致整个事务全部回滚;NESTED嵌套事务可实现局部回滚,不会影响上一个方法的执行结果。

嵌套事务(NESTED)通过保存点(Savepoint) 实现部分回滚,而 REQUIRED 加入事务后无保存点,回滚则整个事务失效。

本质区别

特性 NESTED(嵌套事务) REQUIRED(加入事务)
事务关系 父事务 + 子事务(有保存点) 同一事务(无保存点)
子事务异常回滚 仅回滚子事务,不影响父事务 回滚整个事务
父事务异常回滚 子事务随父事务一起回滚 整个事务回滚
适用场景 需部分回滚的业务(如:核心步骤+非核心步骤) 需完全一致的业务(如:转账的扣款+加款)
相关推荐
索荣荣2 小时前
SpringBoot Starter终极指南:从入门到精通
java·开发语言·springboot
苏涵.2 小时前
三种工厂设计模式
java
小高Baby@2 小时前
Go中常用字段说明
后端·golang·gin
嗯嗯**2 小时前
Neo4j学习3:Java连接图库并执行CQL
java·学习·spring·neo4j·图数据库·驱动·cql
洛阳纸贵2 小时前
JAVA高级工程师--Springboot集成ES、MySQL同步ES的方案、ES分片副本、文档及分片规划
java·spring boot·elasticsearch
树码小子2 小时前
SpringMVC(13):总结
spring
阿猿收手吧!2 小时前
【C++】C++原子类型隐式转换解析
java·c++
追逐梦想的张小年2 小时前
JUC编程02
java·idea