事务隔离级别与回滚规则
结论先行:隔离级别解决并发读写下的三个问题——脏读、不可重复读、幻读;Spring 通过 TransactionDefinition 暴露 5 个常量(DEFAULT 交给数据库),底层映射到 Connection.setTransactionIsolation。回滚规则必须背熟:@Transactional 默认只对 RuntimeException 与 Error 回滚,受检异常(checked)默认不回滚,要让受检异常也回滚必须显式 rollbackFor = Exception.class(或 xxx.class)。
一、隔离级别与并发问题
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 备注 |
|---|---|---|---|---|
| READ_UNCOMMITTED | 可能 | 可能 | 可能 | 几乎不用 |
| READ_COMMITTED | 不会 | 可能 | 可能 | Oracle 等默认 |
| REPEATABLE_READ | 不会 | 不会 | 可能 | MySQL InnoDB 默认;MVCC + 间隙锁基本防住幻读 |
| SERIALIZABLE | 不会 | 不会 | 不会 | 锁最重、并发最差 |
- 脏读:读到别的事务未提交的数据;不可重复读:同一行两次读结果不同(行被改);幻读:同一条件两次查询行数不同(被插入/删除)。
二、代码示例
@Transactional(
isolation = Isolation.REPEATABLE_READ, // 显式指定(一般用 DEFAULT 跟随数据库)
rollbackFor = Exception.class, // 受检异常也回滚 ← 最常写的配置
noRollbackFor = BusinessIgnorableException.class // 个别异常不回滚
)
public void transfer(Account from, Account to, BigDecimal amt) {
// 扣款 + 入账
}
三、回滚规则要点
- 触发条件:异常必须从方法传播到事务拦截器——方法内 catch 掉的异常不会触发回滚;
- 默认回滚:RuntimeException、Error;默认不回滚:受检异常(历史设计:checked 表示调用方须处理的业务预期);
- 判定依据:回滚标记 rollback-only 由传播链上的拦截器根据异常类型设置;
- 多数据源/多事务管理器场景:@Transactional(transactionManager = "xxxTransactionManager") 指定。
常见追问 / 记忆点
- 追问 1:为什么默认不回滚受检异常?答:Spring 沿袭 EJB 时代约定,认为 checked 是"调用方可以处理"的业务预期,不让其轻易毁掉整个事务;工程上为了严谨常统一 rollbackFor = Exception.class。
- 追问 2:MySQL 默认隔离级别与幻读?答:REPEATABLE_READ,InnoDB 通过 MVCC 保证快照读一致、间隙锁/next-key lock 防幻读,因此实际很少出现幻读。
- 记忆点:"隔离级别越严越慢:读未提交 → 读已提交 → 可重复读 → 串行化";"运行时异常默认回滚、受检异常默认不滚,想全滚就 rollbackFor=Exception.class"。