1. 引言
在数据库并发访问场景下,多个事务同时操作同一份数据时,可能会产生脏读、不可重复读、幻读等并发问题。Spring 作为 Java 生态中最主流的应用框架,通过@Transactional注解和TransactionDefinition接口提供了对事务隔离级别的完整支持。本文将深入讲解 Spring 中事务隔离级别的概念、五种隔离级别的区别、底层实现原理以及实际开发中的选型建议。
2. 事务隔离级别基础概念
2.1 什么是事务隔离级别
事务隔离级别(Isolation Level)定义了多个事务并发执行时,一个事务的中间状态对其他事务的可见程度。隔离级别越高,并发控制越严格,数据一致性越好,但并发性能越低;隔离级别越低,并发性能越高,但可能出现数据异常。
2.2 并发事务可能引发的三类问题
在讲解隔离级别之前,先明确三个核心概念:
- 脏读(Dirty Read):一个事务读取到了另一个事务尚未提交的数据。如果后者回滚,前者读取到的就是无效数据。
- 不可重复读(Non-Repeatable Read):一个事务内两次读取同一行数据,结果不一致。原因是另一个事务在两次读取之间修改并提交了该行数据。
- 幻读(Phantom Read):一个事务内两次执行同一查询,返回的结果集行数不一致。原因是另一个事务在两次查询之间插入或删除了满足条件的行。
2.3 隔离级别与并发问题的对应关系
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ_UNCOMMITTED | 可能 | 可能 | 可能 |
| READ_COMMITTED | 不可能 | 可能 | 可能 |
| REPEATABLE_READ | 不可能 | 不可能 | 可能 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 |
3. Spring 中的五种事务隔离级别
Spring 通过org.springframework.transaction.annotation.Isolation枚举定义了五种隔离级别,对应关系如下:
3.1 DEFAULT(默认)
@Transactional(isolation=Isolation.DEFAULT)publicvoiddoWork(){// 业务逻辑}使用底层数据库默认的隔离级别。MySQL 默认是REPEATABLE_READ,Oracle 默认是READ_COMMITTED。这是最常用的配置,推荐优先使用。
3.2 READ_UNCOMMITTED(读未提交)
@Transactional(isolation=Isolation.READ_UNCOMMITTED)publicvoiddoWork(){// 业务逻辑}最低的隔离级别,允许读取尚未提交的数据变更。可能产生脏读、不可重复读和幻读。仅在对数据一致性要求极低、追求极致并发性能的场景下使用。
3.3 READ_COMMITTED(读已提交)
@Transactional(isolation=Isolation.READ_COMMITTED)publicvoiddoWork(){// 业务逻辑}只能读取到其他事务已提交的数据,避免了脏读,但仍可能产生不可重复读和幻读。这是 Oracle、SQL Server 等数据库的默认级别,也是大多数业务系统的推荐选择。
3.4 REPEATABLE_READ(可重复读)
@Transactional(isolation=Isolation.REPEATABLE_READ)publicvoiddoWork(){// 业务逻辑}保证在同一事务内多次读取同一行数据结果一致,避免了脏读和不可重复读,但仍可能产生幻读。这是 MySQL InnoDB 引擎的默认隔离级别,InnoDB 通过间隙锁(Gap Lock)在多数场景下也规避了幻读问题。
3.5 SERIALIZABLE(串行化)
@Transactional(isolation=Isolation.SERIALIZABLE)publicvoiddoWork(){// 业务逻辑}最高的隔离级别,事务完全串行执行,彻底避免了脏读、不可重复读和幻读,但并发性能最低,容易导致锁等待和超时。仅在对数据一致性要求极高、并发量很低的场景下使用。
4. Spring 事务隔离级别的底层实现原理
4.1 隔离级别如何传递到数据库
Spring 本身并不实现数据隔离,而是将隔离级别配置透传给底层数据库连接。核心流程如下: