事务隔离级别所要解决的问题(脏读、不可重复读、幻读),本质上都发生在多个并发事务同时(或交错)访问 *相同数据范围*(如同一张表、同一行、或满足相同查询条件的记录集)时。
一、四种标准隔离级别概览
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 并发性能 | 默认数据库 |
|---|---|---|---|---|---|
| READ UNCOMMITTED | ✅ 允许 | ✅ 允许 | ✅ 允许 | ⭐⭐⭐⭐⭐ | 几乎无 |
| READ COMMITTED | ❌ 禁止 | ✅ 允许 | ✅ 允许 | ⭐⭐⭐⭐ | Oracle, PostgreSQL, SQL Server |
| REPEATABLE READ | ❌ 禁止 | ❌ 禁止 | ⚠️ 标准允许,MySQL InnoDB 实际禁止 | ⭐⭐⭐ | MySQL |
| SERIALIZABLE | ❌ 禁止 | ❌ 禁止 | ❌ 禁止 | ⭐ | 少数关键系统 |
💡 脏读:读到未提交的数据
不可重复读:同一事务内多次读同一行,结果不同(被 UPDATE)
幻读:同一事务内多次执行相同范围查询,结果集行数不同(被 INSERT/DELETE)
二、各隔离级别详解 + 示例
1. READ UNCOMMITTED(读未提交)
✅ 特点
- 可读取其他事务 未提交 的数据(脏读)
- 不加读锁,性能最高,一致性最差
⚠️ 风险场景
1// 会话 A
2@Transactional(isolation = Isolation.READ_UNCOMMITTED)
3public void read() {
4 Account a = repo.findById(1L); // 读到 balance = 999
5 // 但会话 B 还没 COMMIT!甚至可能 ROLLBACK!
6}
1-- 会话 B
2START TRANSACTION;
3UPDATE accounts SET balance = 999 WHERE id = 1;
4-- 未 COMMIT → 会话 A 已读到 999(脏数据!)
5ROLLBACK; -- 真实余额仍是 100
📌 适用场景
- 日志分析、监控指标等容忍错误的只读场景
- 绝不用于金融、订单、账户等业务!
2. READ COMMITTED(读已提交)✅ 最常用
✅ 特点
- 只能读已提交数据(防脏读)
- 每次 SELECT 都读最新已提交值 → 同一事务内多次读可能不同(不可重复读)
- 如果其他事务正在修改但 尚未提交,读操作会 跳过未提交的更改,返回上一个已提交的值 (对比读未提交: 可能直接返回其他事务尚未提交的修改值*(即“脏数据”),而不是上一个已提交的值。 )
🧪 示例(不可重复读)
1@Transactional(isolation = Isolation.READ_COMMITTED)
2public void check() {
3 Account a1 = repo.findById(1L); // balance = 100
4
5 // 此时会话 B: UPDATE balance = 200; COMMIT;
6
7 Account a2 = repo.findById(1L); // balance = 200 ❗️变了
8}
⚠️ 风险
“读-改-写”模式危险:
1if (account.getBalance() >= 100) { 2 account.setBalance(account.getBalance() - 100); // 基于旧快照,可能透支! 3 repo.save(account); 4}✅ 安全做法
1-- 原子更新(推荐!)
2UPDATE accounts
3SET balance = balance - 100
4WHERE id = 1 AND balance >= 100;
5-- 检查 affected rows == 1
📌 适用场景
- 大多数 Web 应用、API 服务、电商下单等
3. REPEATABLE READ(可重复读)✅ MySQL 默认
✅ 特点(MySQL InnoDB)
- 普通 SELECT 基于事务开始时的 MVCC 快照
- 同一事务内多次读同一行,结果一致(防不可重复读)
- 通过 Next-Key Lock 防止幻读(比 SQL 标准更强)
🧪 示例(快照读一致)
1@Transactional(isolation = Isolation.REPEATABLE_READ)
2public void observe() {
3 Account a1 = repo.findById(1L); // 100
4
5 // 会话 B: UPDATE balance = 200; COMMIT;
7 Account a2 = repo.findById(1L); // 仍为 100 ✅
// “当前读”
2 SELECT * FROM accounts WHERE id = 1 FOR UPDATE; -- 返回 200!
// 或在本会话内 update balance = 200
Account a2 = repo.findById(1L); //为 200 ✅
8}
⚠️ 注意:当前读(Current Read)会看到新值!
以下操作属于 当前读,会读取最新已提交的数据,并加锁:
SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEUPDATEDELETEINSERT(间接影响)
📌 适用场景
- 报表生成、账务对账、金融核对等需读一致性的场景
4. SERIALIZABLE(串行化)
✅ 特点
- 完全消除并发异常,效果等价于串行执行
- 代价:性能极低,易死锁
🔒 实现方式(因数据库而异)
| 数据库 | 实现方式 |
|---|---|
| MySQL InnoDB | 所有 SELECT 自动转为 SELECT ... LOCK IN SHARE MODE(加共享锁) |
| PostgreSQL | 使用 SSI(Serializable Snapshot Isolation),不加读锁,提交时检测冲突 |
🧪 MySQL 示例(自动加锁)
1 @Transactional(isolation = Isolation.SERIALIZABLE)
2 public void read() {
3 repo.findById(1L); // 实际执行:SELECT ... LOCK IN SHARE MODE
4}
→ 其他事务无法同时 UPDATE id=1,会被阻塞。
📌 适用场景
- 银行核心账务、法律审计等绝对一致性要求 + 低并发场景
三、关键补充:锁的粒度问题
❓ accountRepo.findById(1L) 会锁表吗?
| 场景 | 是否加锁? | 锁什么? |
|---|---|---|
| 普通 SELECT(默认) | ❌ 不加锁 | 无 |
FOR UPDATE / LOCK IN SHARE MODE |
✅ 加锁 | 仅 id=1 这一行(前提是 id 是索引/主键) |
SERIALIZABLE(MySQL) |
✅ 自动加共享锁 | 仅 id=1 这一行 |
| WHERE 条件无索引 + FOR UPDATE | ⚠️ 可能锁多行或整表 | 因全表扫描 |
✅ 结论:只要
id是主键(InnoDB 聚簇索引),只会锁一行,不会锁表。
四、最佳实践总结
| 场景 | 推荐隔离级别 | 建议 |
|---|---|---|
| 普通 Web API、CRUD | READ COMMITTED |
✅ 最平衡 |
| 金融对账、报表 | REPEATABLE READ |
✅ 保证读一致性 |
| 极高一致性 + 低并发 | SERIALIZABLE |
⚠️ 谨慎使用 |
| 监控/日志(容忍错误) | READ UNCOMMITTED |
🚫 避免用于业务逻辑 |
| 防并发修改 | 显式加锁 or 原子 SQL | UPDATE ... WHERE version = ? 或 FOR UPDATE |
Spring Boot 设置方式
1@Service
2public class AccountService {
3
4 // 读已提交(推荐大多数场景)
5 @Transactional(isolation = Isolation.READ_COMMITTED)
6 public void transfer() { /* ... */ }
7
8 // 可重复读(MySQL 默认,适合对账)
9 @Transactional(isolation = Isolation.REPEATABLE_READ)
10 public void generateReport() { /* ... */ }
11
12 // 串行化(慎用!)
13 @Transactional(isolation = Isolation.SERIALIZABLE)
14 public void criticalAdjust() { /* ... */ }
15}给个饭钱?
- Post link: http://sovzn.github.io/2026/01/20/%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.



若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub Issues