Spring 事务隔离级别 ⭐⭐⭐⭐⭐
面试频率:★★★★★
工作频率:★★★★☆
⚡30 秒速记
🟢 一句话
事务隔离级别决定多个事务并发执行时,一个事务能够看到其他事务什么状态的数据。
并发事务常见三个问题:
脏读
= 读到了别人还没提交的数据
不可重复读
= 同一事务中,同一行数据前后读取结果不同
幻读
= 同一事务中,两次条件查询,符合条件的行发生变化
四种隔离级别:
READ_UNCOMMITTED
↓
READ_COMMITTED
↓
REPEATABLE_READ
↓
SERIALIZABLE
隔离越来越强
并发能力通常越来越低
一、为什么需要事务隔离?
假设数据库:
余额 = 1000
现在:
事务A 事务B
查询余额1000
查询余额1000
修改余额
再次查询
两个事务同时操作数据库时:
会产生很多问题。
例如:
A还没提交的数据
B能不能看到?
或者:
B第一次查1000
A修改并提交
B第二次查
应该看到1000还是500?
这些问题:
就是:
并发事务问题
Spring / 数据库通过:
事务隔离级别
控制事务之间的数据可见性。
二、脏读 Dirty Read ⭐⭐⭐⭐⭐
一句话:
读取到了其他事务还没有提交的数据。
例如:
数据库:
余额 = 1000
事务A:
查询余额1000
↓
修改余额500
↓
还没有commit
这时候事务B:
查询余额
↓
500
但是事务A突然:
rollback
数据库又变成:
余额 = 1000
那么事务B刚才读到的:
500
其实是:
从来没有真正提交的数据
所以叫:
脏读。
流程
事务A 事务B
余额1000
修改成500
↓
未提交
↓
查询
↓
500
↓
rollback
↓
恢复1000
事务B:
读到了一个最终不存在的数据
速记
脏读
=
读到了别人未提交的数据
关键词:
未提交。
三、不可重复读 Non-repeatable Read ⭐⭐⭐⭐⭐
一句话:
同一个事务中,两次读取同一行数据,结果不一样。
例如:
事务A:
第一次查询:
张三余额 = 1000
这时候事务B:
修改张三余额
1000 → 500
↓
commit
事务A:
再次查询:
张三余额 = 500
结果:
第一次:
1000
第二次:
500
同一个事务:
同一行数据:
两次读取结果不同
这叫:
不可重复读。
流程
事务A 事务B
查询张三
余额1000
修改张三
余额500
↓
commit
再次查询张三
余额500
速记
不可重复读
=
同一行数据
↓
被别人修改并提交
↓
前后读取结果不同
关键词:
修改。
四、幻读 Phantom Read ⭐⭐⭐⭐⭐
一句话:
同一个事务中,两次按照相同条件查询,符合条件的数据行发生变化。
例如:
事务A:
SELECT *
FROM user
WHERE age > 18;
第一次:
10个人
这时候事务B:
新增一个:
age = 20
↓
commit
事务A:
再次执行:
SELECT *
FROM user
WHERE age > 18;
结果:
11个人
事务A:
第一次:
10行
第二次:
11行
就像:
突然多出来一行
所以叫:
幻读。
流程
事务A 事务B
查询age > 18
10个人
新增一个20岁的人
commit
再次查询age > 18
11个人
速记
幻读
=
条件查询
↓
符合条件的数据行发生变化
关键词:
行发生变化 / 新增删除。
五、不可重复读和幻读区别 ⭐⭐⭐⭐⭐
这个面试非常容易问。
不可重复读
关注:
同一行数据的内容变了
例如:
第一次:
张三 1000
第二次:
张三 500
还是:
张三这一行
但是:
这一行的数据被修改了
幻读
关注:
符合条件的数据行发生变化
例如:
第一次:
10个人
第二次:
11个人
出现了:
新的符合条件的数据行
一句话区别
不可重复读
=
同一行的数据变了
幻读
=
符合条件的行变了
可以粗略记:
不可重复读 → UPDATE
幻读 → INSERT / DELETE
注意:
这只是帮助记忆,不是严格定义。
六、三个问题最终对比 ⭐⭐⭐⭐⭐
脏读
↓
别人没提交
我就读到了
不可重复读
↓
别人修改并提交
↓
我前后读同一行不一样
幻读
↓
别人新增 / 删除符合条件的数据并提交
↓
我前后条件查询的结果集发生变化
七、四种事务隔离级别 ⭐⭐⭐⭐⭐
SQL 标准定义四种主要隔离级别:
READ_UNCOMMITTED
READ_COMMITTED
REPEATABLE_READ
SERIALIZABLE
隔离强度:
READ_UNCOMMITTED
↓
READ_COMMITTED
↓
REPEATABLE_READ
↓
SERIALIZABLE
越来越强
通常:
隔离越强
↓
并发限制越多
↓
性能 / 并发能力可能越低
八、READ_UNCOMMITTED
中文:
读未提交。
意思:
其他事务还没有commit
↓
我就可以读取它修改的数据
例如:
事务A:
1000 → 500
还没commit
事务B:
已经能读到500
所以:
可能发生:
脏读 ❌
不可重复读 ❌
幻读 ❌
这里:
❌ = 不能防止
隔离级别最低。
九、READ_COMMITTED ⭐⭐⭐⭐⭐
中文:
读已提交。
意思:
其他事务必须commit以后
↓
我才能读取到它的数据
所以:
事务A:
1000 → 500
还没commit
事务B:
看不到500
只有:
事务A commit
以后:
事务B才能看到:
500
因此:
脏读:
✅ 可以防止
但是:
不可重复读:
❌ 仍可能发生
例如:
事务A:
第一次查询1000
事务B:
修改500
commit
事务A:
再次查询500
十、REPEATABLE_READ ⭐⭐⭐⭐⭐
中文:
可重复读。
核心:
在同一个事务中的一致性读取,可以保持稳定的读取视图。
例如:
事务A:
第一次查询余额1000
事务B:
修改余额500
↓
commit
事务A再次进行一致性读取:
仍然可以看到1000
所以:
可以防止:
脏读 ✅
不可重复读 ✅
十一、MySQL 默认隔离级别 ⭐⭐⭐⭐⭐
MySQL InnoDB 默认:
REPEATABLE READ
简称:
RR
MySQL InnoDB:
还通过:
MVCC
以及在特定锁定读 / 写操作中的:
锁机制
处理很多并发读取和幻读相关问题。
所以面试时:
不要简单说:
MySQL RR一定会发生幻读
更准确:
SQL 标准层面,REPEATABLE READ 主要保证避免脏读和不可重复读;MySQL InnoDB 在 RR 下结合 MVCC 和锁机制,对幻读也做了进一步处理。
十二、SERIALIZABLE
中文:
串行化。
这是:
最高的标准隔离级别
可以粗略理解:
事务A
↓
正在执行
事务B
↓
某些冲突操作需要等待
事务A结束
↓
事务B继续
它会对并发事务施加更严格的限制。
所以:
并发问题最少
但是
并发能力通常最低
可以防止:
脏读 ✅
不可重复读 ✅
幻读 ✅
十三、四种隔离级别对比 ⭐⭐⭐⭐⭐
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ_UNCOMMITTED | ❌ | ❌ | ❌ |
| READ_COMMITTED | ✅ | ❌ | ❌ |
| REPEATABLE_READ | ✅ | ✅ | 标准层面仍需注意 |
| SERIALIZABLE | ✅ | ✅ | ✅ |
说明:
✅ = 可以防止
❌ = 可能发生
对于 MySQL InnoDB:
REPEATABLE_READ
还结合:
MVCC
锁机制
进一步处理幻读问题。
十四、Spring 怎么设置隔离级别? ⭐⭐⭐⭐⭐
使用:
@Transactional(
isolation = Isolation.READ_COMMITTED
)
public void createOrder() {
}
例如:
@Transactional(
isolation = Isolation.REPEATABLE_READ
)
public void createOrder() {
}
Spring 提供:
Isolation.DEFAULT
Isolation.READ_UNCOMMITTED
Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE
十五、Isolation.DEFAULT 是什么? ⭐⭐⭐⭐⭐
Spring:
@Transactional(
isolation = Isolation.DEFAULT
)
默认就是:
Isolation.DEFAULT
意思不是:
Spring规定了一个固定隔离级别
而是:
使用底层数据库 / 事务资源的默认隔离级别。
例如:
MySQL InnoDB 默认通常是:
REPEATABLE READ
所以:
Spring DEFAULT
↓
使用数据库默认
↓
MySQL InnoDB
↓
REPEATABLE READ
十六、Propagation 和 Isolation 区别 ⭐⭐⭐⭐⭐
这个一定要分清楚。
你前面学过:
@Transactional(
propagation = Propagation.REQUIRED
)
Propagation:
解决:
一个事务方法调用另一个事务方法时,事务怎么处理。
例如:
A调用B
↓
B加入A?
还是
B创建新事务?
Isolation:
@Transactional(
isolation = Isolation.READ_COMMITTED
)
解决:
多个事务并发执行时,事务之间的数据可见性和隔离程度。
所以:
Propagation
=
事务怎么传
Isolation
=
事务之间怎么隔离
十七、举个完整例子
例如:
@Transactional(
propagation = Propagation.REQUIRES_NEW,
isolation = Isolation.READ_COMMITTED
)
public void saveLog() {
}
这里:
propagation
决定:
这个方法使用哪个事务?
因为:
REQUIRES_NEW
所以:
创建新事务
而:
isolation
决定:
这个新事务和其他并发事务之间
能看到什么数据?
因为:
READ_COMMITTED
所以:
只能读取别人已经提交的数据
两个参数:
解决的是完全不同的问题。
十八、MVCC 是什么? ⭐⭐⭐⭐
MVCC:
Multi-Version Concurrency Control
中文:
多版本并发控制。
现在不用深入源码。
先理解:
数据库为了:
提高并发能力
不会所有读取都简单粗暴地:
加锁
而是可以保存:
数据的不同版本
让不同事务:
根据自己的:
Read View
读取合适的数据版本。
可以粗略理解:
数据库中:
张三 = 1000
事务B修改:
张三 = 500
↓
数据库保留版本信息
事务A:
根据自己的读取视图:
可能仍然看到1000
其他事务:
根据自己的读取视图:
可能看到500
所以:
MVCC
↓
让读写并发能力更高
后面如果专门学 MySQL:
再详细学:
undo log
Read View
版本链
快照读
当前读
今天先知道:
MySQL InnoDB 的可重复读和 MVCC 密切相关。
十九、快照读和当前读
这个今天先有印象。
普通查询:
SELECT *
FROM user
WHERE id = 1;
通常属于:
快照读
会利用:
MVCC
读取合适的数据版本。
如果:
SELECT *
FROM user
WHERE id = 1
FOR UPDATE;
属于:
当前读
它需要读取:
当前最新的数据
并涉及:
加锁
所以 MySQL 的:
幻读
MVCC
间隙锁
Next-Key Lock
这些问题:
都和:
快照读
当前读
有关。
这部分以后放到 MySQL 专门学。
二十、实际工作怎么选隔离级别?
一般:
不要没事就手动修改隔离级别。
大多数项目:
使用数据库默认隔离级别
就可以。
如果真的出现:
并发一致性要求
性能要求
特殊业务场景
再根据实际情况:
选择隔离级别和锁方案。
🎤 面试回答(30秒)
数据库事务主要有四种标准隔离级别:READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE。
它们主要用于解决并发事务中的脏读、不可重复读和幻读问题。
脏读是读取到其他事务未提交的数据;不可重复读是同一个事务中读取同一行数据前后结果不同;幻读是同一个事务中两次条件查询的结果集发生变化。
Spring 可以通过 @Transactional 的 isolation 属性设置隔离级别,默认 Isolation.DEFAULT 表示使用底层数据库的默认隔离级别。MySQL InnoDB 默认通常是 REPEATABLE READ,并结合 MVCC 和锁机制处理并发读取问题。
🎯 面试追问
Q1:什么是脏读?
答:
读取到了其他事务还没有提交的数据。
Q2:什么是不可重复读?
答:
同一个事务中
同一行数据
前后两次读取结果不同。
Q3:什么是幻读?
答:
同一个事务中
两次相同条件查询
符合条件的数据行发生变化。
Q4:不可重复读和幻读有什么区别?
答:
不可重复读:
同一行的数据内容变了
幻读:
符合条件的数据行发生变化
Q5:四种隔离级别是什么?
答:
READ_UNCOMMITTED
READ_COMMITTED
REPEATABLE_READ
SERIALIZABLE
Q6:MySQL InnoDB 默认隔离级别是什么?
答:
REPEATABLE READ
Q7:Spring 默认隔离级别是什么?
答:
Isolation.DEFAULT
表示:
使用底层数据库默认隔离级别
Q8:Propagation 和 Isolation 有什么区别?
答:
Propagation
↓
解决事务怎么传播
Isolation
↓
解决并发事务之间怎么隔离
Q9:什么是 MVCC?
答:
多版本并发控制。数据库通过维护数据版本和读取视图,让不同事务读取合适的数据版本,从而提高读写并发能力。
Q10:什么是快照读?
答:
普通 SELECT:
SELECT *
FROM user;
通常属于:
快照读
主要利用:
MVCC
读取合适的数据版本。
Q11:什么是当前读?
答:
例如:
SELECT *
FROM user
WHERE id = 1
FOR UPDATE;
需要读取:
当前最新数据
并进行相应加锁。
⚠️ 工作踩坑
1. 把事务传播和隔离级别搞混
错误:
REQUIRED
REPEATABLE_READ
不是一类东西。
正确:
REQUIRED
↓
Propagation
↓
事务怎么传
REPEATABLE_READ
↓
Isolation
↓
事务之间怎么隔离
2. 认为隔离级别越高越好
错误。
隔离越强
↓
并发限制通常越多
↓
性能可能下降
应该:
根据业务一致性要求选择。
3. 简单认为 MySQL RR 一定有幻读
不够准确。
MySQL InnoDB 在:
REPEATABLE READ
下结合:
MVCC
锁机制
对幻读问题进行了进一步处理。
4. 随便修改数据库隔离级别
不要因为:
SERIALIZABLE最安全
就直接使用最高隔离级别。
可能导致:
锁等待增加
并发能力下降
性能下降
大多数业务:
先使用数据库默认配置。
❓ 自测
-
什么是事务隔离级别?
-
为什么需要事务隔离?
-
什么是脏读?
-
什么是不可重复读?
-
什么是幻读?
-
不可重复读和幻读有什么区别?
-
四种标准事务隔离级别分别是什么?
-
READ_UNCOMMITTED 有什么问题?
-
READ_COMMITTED 能解决什么问题?
-
REPEATABLE_READ 能解决什么问题?
-
SERIALIZABLE 有什么特点?
-
MySQL InnoDB 默认隔离级别是什么?
-
Spring 的 Isolation.DEFAULT 是什么意思?
-
Propagation 和 Isolation 有什么区别?
-
MVCC 是什么?
-
MVCC 为什么可以提高并发能力?
-
什么是快照读?
-
什么是当前读?
-
SELECT ... FOR UPDATE 属于什么读?
-
为什么不能简单认为隔离级别越高越好?