Skip to content

Spring 事务有几个隔离级别? ​

  • 标签:后端、Spring

🏛 业务架构师 ​

Spring 事务隔离级别就是数据库标准隔离级别的抽象,共 5 个:

  • DEFAULT:用底层数据库默认级别(MySQL InnoDB 默认 REPEATABLE READ,PostgreSQL/Oracle 默认 READ COMMITTED);
  • READ_UNCOMMITTED:读未提交,存在脏读;
  • READ_COMMITTED:读已提交,解决脏读,仍有不可重复读;
  • REPEATABLE_READ:可重复读,解决不可重复读,InnoDB 通过 MVCC + 间隙锁基本解决幻读;
  • SERIALIZABLE:串行化,最强隔离、并发能力最差。

架构师视角我更强调业务正确性选型而不是背概念:

  1. 默认级别基本够用:绝大多数业务(订单、支付流水)在 DB 默认隔离级别 + 唯一索引 + 乐观锁(version 字段)下就是正确的。显式调高级别前先问:业务真的发生了脏读/幻读事故吗?
  2. 金融对账类场景才有讨论价值:比如"读余额 + 计算 + 扣减"必须防不可重复读,要么 RR + 行锁(select ... for update),要么乐观锁重试,我会带着业务方算并发冲突率来选;
  3. 隔离级别是性能换正确性:SERIALIZABLE 在互联网并发量下基本不可用,业务上通常用"降低隔离假设 + 应用层补偿(对账、幂等)"来达成一致性,而不是无脑拉高隔离级别。

一句话:隔离级别是业务风险决策,先量化脏数据的业务代价(资损 or 显示瑕疵),再决定付出多少并发性能。

🏗 系统架构师 ​

Spring 提供五种与 JDBC 对齐的隔离级别:ISOLATION_DEFAULT(跟随数据源默认)加读未提交、读已提交、可重复读、串行化。Spring 只是透传,真正实现靠数据库(MySQL InnoDB 默认 RR,PostgreSQL/Oracle 默认 RC)。

架构选型逻辑(我实际的做法):

  • 隔离级别是全局参数,不动数据库缺省,按业务点用 @Transactional(isolation=...) 收窄调整;
  • 金融扣减类:RR/RC 加乐观锁版本号,或 SELECT ... FOR UPDATE 缩小锁范围;吞吐优先的业务:RC 加唯一约束兜底,减少间隙锁冲突——不少互联网公司把 MySQL 整体调到 RC,就是拿「不可重复读」换并发;
  • 代价意识:隔离每升一档都是拿性能换正确性,串行化与悲观锁在高并发下吞吐可能掉一个数量级。

分布式语境(加分点):跨服务后本地事务的隔离性无法跨连接传播,跨服务一致性只能靠 Saga/TCC 的业务隔离或全局锁(Seata)近似——这是微服务拆分前必须算进去的一致性成本。

💻 全栈开发专家 ​

Spring 是对 JDBC 隔离级别的透传,Isolation 枚举 5 个值:

java
@Transactional(isolation = Isolation.READ_COMMITTED)
public void createOrder() { ... }
  • DEFAULT:跟随数据库。MySQL 默认 REPEATABLE_READ,PostgreSQL/Oracle 默认 READ_COMMITTED
  • READ_UNCOMMITTED:脏读都拦不住,基本没人用
  • READ_COMMITTED:防脏读
  • REPEATABLE_READ:防脏读 + 不可重复读,InnoDB 借 MVCC + 间隙锁基本防住幻读
  • SERIALIZABLE:全串行,性能最差

工程实情:我几乎从不在代码里显式设置 isolation,统一 DEFAULT 让 DBA 定基线。并发一致性更多用乐观锁解决:

sql
UPDATE account SET balance = balance - 100, version = version + 1
WHERE id = 1 AND version = 5;
-- 影响行数为 0 说明并发冲突,业务侧重试

理由:隔离级别调高是全局锁代价,乐观锁只影响冲突的那几行。另外 RR 级别下长事务容易撞间隙锁死锁——上线前我会用 SHOW ENGINE INNODB STATUS 看死锁日志,把"锁等待"消灭在测试阶段。