Spring 事务的隔离级别(Isolation)有哪些?
Spring 事务的隔离级别(Isolation Level)是为了解决在并发事务执行时可能出现的各种数据一致性问题(如脏读、不可重复读、幻读)。
需要强调的是,Spring 并不直接实现事务隔离级别,而是将其委托给底层的数据库去实现。Spring 的 Isolation 枚举类中定义了 5 种隔离级别(包含 4 种标准数据库隔离级别 + 1 个 Spring 特有默认级别)。
在详细介绍隔离级别之前,我们需要先了解并发事务会引发的 3 种常见问题:
- 脏读(Dirty Read): 事务 A 读取了事务 B 尚未提交的数据。如果事务 B 回滚,事务 A 读取到的就是无效数据。
- 不可重复读(Non-Repeatable Read): 事务 A 多次读取同一条记录,在此期间事务 B 修改(Update/Delete)并提交了该记录,导致事务 A 多次读取的结果不一致。
- 幻读(Phantom Read): 事务 A 按照某个条件多次查询数据,在此期间事务 B 插入(Insert)了符合该条件的新记录并提交,导致事务 A 后来查询时多出了“幻影”一样的记录。
Spring 的 5 种事务隔离级别
在 Spring 中,可以通过 @Transactional(isolation = Isolation.xxx) 来设置:
1. Isolation.DEFAULT(Spring 默认)
- 含义: 使用底层数据库默认的隔离级别。
- 说明: 如果你不显式设置隔离级别,Spring 就会按这个执行。对于 MySQL,默认是
REPEATABLE_READ;对于 Oracle / PostgreSQL / SQL Server,默认是READ_COMMITTED。
2. Isolation.READ_UNCOMMITTED(读未提交)
- 含义: 最低的隔离级别。允许一个事务读取另一个事务尚未提交的数据。
- 解决的问题: 无。
- 存在的问题: 会出现脏读、不可重复读、幻读。
- 应用场景: 极少使用,因为数据安全性太差。
3. Isolation.READ_COMMITTED(读已提交)
- 含义: 保证一个事务只能读取到另一个事务已经提交的数据。
- 解决的问题: 阻止了脏读。
- 存在的问题: 仍然会出现不可重复读和幻读。
- 应用场景: 大多数主流数据库(如 Oracle、PostgreSQL)的默认隔离级别。
4. Isolation.REPEATABLE_READ(可重复读)
- 含义: 保证在一个事务内多次读取同一数据时,结果始终是一致的(即便其他事务修改了该数据并提交)。
- 解决的问题: 阻止了脏读和不可重复读。
- 存在的问题: 理论上仍然会出现幻读。
- 应用场景: MySQL (InnoDB) 的默认隔离级别。(注:MySQL 的 InnoDB 引擎在这个级别下,通过 MVCC 和 Next-Key 间隙锁机制,实际上已经很大程度上解决了幻读问题)。
5. Isolation.SERIALIZABLE(串行化)
- 含义: 最高的隔离级别。完全服从 ACID 规范。它要求所有事务串行(排队)执行,不能并发。
- 解决的问题: 阻止了脏读、不可重复读、幻读。
- 存在的问题: 性能极差,会导致大量的锁竞争和超时。
- 应用场景: 极少使用,仅在对数据一致性要求极高且并发量极低的场景下使用。
总结与对比表
隔离级别 (Isolation) |
脏读 (Dirty Read) | 不可重复读 (Non-Repeatable) | 幻读 (Phantom Read) | 性能 |
|---|---|---|---|---|
| READ_UNCOMMITTED | 允许 | 允许 | 允许 | 最高 |
| READ_COMMITTED | 阻止 | 允许 | 允许 | 较高 |
| REPEATABLE_READ | 阻止 | 阻止 | 允许* | 较低 |
| SERIALIZABLE | 阻止 | 阻止 | 阻止 | 最低 |
(注:星号 代表在标准 SQL 规范中允许幻读,但在 MySQL InnoDB 引擎中,REPEATABLE_READ 级别下通常不会发生幻读)*
代码示例
在 Spring Boot 中,通常这样使用:
java
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
// 设置为读已提交级别
@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateUser() {
// 业务逻辑
}
}
右滑查看面试常问