sql标准定义四种隔离级别:read_uncommitted允许脏读、不可重复读和幻读;read_committed防止脏读但仍有不可重复读和幻读;repeatable_read防止前两者,mysql通过mvcc+间隙锁基本避免幻读;serializable完全防止三类问题但并发性能最低。

Java 本身不直接解决脏读、不可重复读和幻读,而是通过声明事务隔离级别,把控制权交给数据库。真正起作用的是数据库引擎(比如 MySQL InnoDB、PostgreSQL)基于该级别实现的机制,如 MVCC 或锁。
四种隔离级别各自能挡住什么
SQL 标准定义了四个级别,Java 应用可通过 JDBC 或 Spring 的 @Transactional 指定,效果取决于数据库是否支持并实际生效:
- READ_UNCOMMITTED:不防任何问题。允许读未提交数据 → 脏读、不可重复读、幻读都可能发生。
- READ_COMMITTED:防止脏读。每次 SELECT 都生成新快照(如 PostgreSQL)或只读已提交版本(如 Oracle),但同一事务内两次查同一条记录可能不同 → 不可重复读、幻读仍存在。
- REPEATABLE_READ:防止脏读和不可重复读。MySQL InnoDB 在此级别默认用 MVCC + 间隙锁(Gap Lock),对范围查询加锁,基本拦截幻读;但严格按 SQL 标准,它并不保证完全杜绝幻读(例如快照读下插入新行仍可见)。
- SERIALIZABLE:所有读操作自动加共享锁,写操作加排他锁,强制串行执行 → 三类问题全防,但并发能力极低,一般只用于核对账目等极敏感场景。
Java 中怎么设置隔离级别
两种常用方式,注意设置后需确认数据库实际生效:
- JDBC 层:获取 Connection 后调用 setTransactionIsolation(Connection.TRANSACTION_ISOLATION_REPEATABLE_READ);
- Spring 声明式事务:在方法上加 @Transactional(isolation = Isolation.REPEATABLE_READ);
- 若数据库不支持所设级别(如 H2 默认不支持 SERIALIZABLE),可能静默降级,建议结合数据库日志或
SELECT @@tx_isolation(MySQL)验证。
为什么 MySQL 的 REPEATABLE_READ 能“看起来”防幻读
这不是 Java 的功劳,而是 InnoDB 的实现细节:
- 普通 SELECT 是快照读(Snapshot Read),基于事务开启时的一致性视图,不会看到其他事务新增的行;
- 但若执行 SELECT ... FOR UPDATE 或 UPDATE WHERE ... 这类当前读(Current Read),InnoDB 会加临键锁(Next-Key Lock = 行锁 + 间隙锁),锁住查询范围,阻止其他事务在该范围内插入新行;
- 所以“防幻读”是有条件的:依赖具体 SQL 类型 + 引擎行为,并非隔离级别本身在标准层面的承诺。
光靠隔离级别不够时怎么办
当业务逻辑对一致性要求极高,而隔离级别又无法覆盖全部边界(比如统计总数时被插入干扰),可以补充手段:
- 关键路径显式加锁:如 SELECT * FROM order WHERE status = 'pending' FOR UPDATE,让后续插入/更新阻塞;
- 用应用层缓存替代实时查:对“总条数”“最新汇总值”等,加版本号或时间戳缓存,避开数据库幻读场景;
- 业务层重试或校验:比如扣款前再次 SELECT 校验余额,配合乐观锁(version 字段)避免覆盖写。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











