事务的隔离级别是一个高频考点,它涉及数据库事务并发时的行为控制。以下是关于事务隔离级别的的核心知识点:
事务的四大特性(ACID)
首先需要明确事务的四大特性,这是理解隔离级别的基础:
- 原子性(Atomicity) :事务是不可分割的最小单位,要么全执行,要么全不执行
- 一致性(Consistency) :事务执行前后,数据始终保持符合业务规则
- 隔离性(Isolation) :多个个事务并发执行时,彼此不产生干扰
- 持久性(Durability) :事务提交后,数据变化永久保存
并发事务可能产生的问题
当多个事务并发操作相同数据时,可能出现以下问题:
- 脏读:一个事务读取到另一个事务未提交的数据
- 不可重复读:一个事务内多次读取同一数据,结果不一致(被其他事务修改并提交)
- 幻读:一个事务内多次查询同一范围数据,结果行数不一致(被其他事务插入 / 删除并提交)
数据库的四大隔离级别
为解决上述问题,SQL 标准定义了 4 种隔离级别(由低到高):
-
读未提交(Read Uncommitted)
- 允许读取未提交的数据
- 可能出现:脏读、不可重复读、幻读
- 性能最好,但安全性最低
-
读已提交(Read Committed)
- 只能读取已提交的数据
- 解决:脏读
- 可能出现:不可重复读、幻读
- 是 Oracle 等数据库的默认隔离级别
-
可重复读(Repeatable Read)
- 保证同一事务内多次读取同一数据结果一致
- 解决:脏读、不可重复读
- 可能出现:幻读
- 是 MySQL InnoDB 的默认隔离级别
-
串行化(Serializable)
- 事务完全串行执行,不并发
- 解决:所有并发问题
- 安全性最高,但性能最差
Java 中设置隔离级别
在 Java 中可以通过 JDBC 或 Spring 来设置事务隔离级别:
JDBC 方式:
java
运行
// 设置隔离级别为读已提交
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
Spring 声明式事务:
java
运行
@Transactional(isolation = Isolation.READ_COMMITTED)
public void doSomething() {
// 业务逻辑
}
结合「银行转账」这个场景,一步步拆解每个隔离级别下会发生什么,保证你能看懂~
先明确场景:两个事务同时操作同一账户
假设账户 小明的余额是 1000 元,现在有两个事务同时运行:
-
事务 A:小明给小红转账 500 元(操作:余额减 500)
-
事务 B:查询小明的余额(想知道他还剩多少钱)
我们看看在不同隔离级别下,事务 B 会读到什么结果,以及可能出现的问题。
1. 读未提交(Read Uncommitted):能看到 “半成品”
-
过程:
- 事务 A 开始执行,先把小明的余额从 1000 改成 500(但没提交,相当于 “还在操作中,没最终确认”)。
- 此时事务 B 查询小明的余额,直接读到了 500 元(这是事务 A 没提交的 “半成品” 数据)。
- 突然,事务 A 发现转错人了,执行 “回滚”(取消操作),小明的余额变回 1000 元。
- 事务 B 之前读到的 “500 元” 其实是无效的,因为事务 A 最终没提交。
-
问题:事务 B 读到了 “脏数据”(脏读)。就像你去厨房看妈妈做饭,妈妈刚把菜倒进锅还没炒熟,你就说 “菜做好了”,结果妈妈发现盐放多了,把菜倒了重新做 —— 你之前看到的 “快做好的菜” 其实是会被丢弃的。
2. 读已提交(Read Committed):只能看到 “成品”,但可能变
-
过程:
- 事务 A 开始执行,把余额改成 500(未提交),此时事务 B 查询,只能读到 1000 元(因为事务 A 没提交,不算 “成品”)。
- 事务 A 确认没问题,提交了操作(余额正式变成 500)。
- 事务 B 再次查询,读到了 500 元(这次是 “成品” 数据)。
-
结果:解决了 “脏读”(不会读到没提交的数据),但事务 B 两次查询结果不一样(第一次 1000,第二次 500),这就是 “不可重复读”。就像你早上看冰箱里有瓶可乐(1000 元),中午妈妈把可乐喝了(提交了事务 A),你下午再看,可乐没了(500 元)—— 同一时间段内,两次看的结果不一样。
3. 可重复读(Repeatable Read):一旦看好,中间不变
-
过程:
- 事务 B 第一次查询,读到小明余额 1000 元(此时事务 B “记住” 了这个结果)。
- 事务 A 执行转账,把余额改成 500 并提交(此时数据库里实际已经是 500 了)。
- 事务 B 再次查询,仍然读到 1000 元(因为它 “记住” 了第一次的结果,不受其他事务影响)。
- 直到事务 B 自己结束(提交或回滚),再次查询才会读到 500 元。
-
结果:解决了 “不可重复读”(同一事务内多次读结果一致),但可能有 “幻读”(针对 “范围查询”)。
比如:事务 B 想统计 “余额> 800 的用户”,第一次查只有小明(1000 元);此时事务 A 给小刚转账,让小刚余额变成 900 并提交;事务 B 再次统计,还是只有小明(但实际小刚已经符合条件了)—— 就像你数苹果,数的时候有 3 个,数完后妈妈偷偷放了 1 个,但你没发现,以为还是 3 个。
4. 串行化(Serializable):排队操作,谁也别打扰谁
-
过程:
- 事务 A 先开始,系统给小明的账户 “上锁”,不允许其他事务操作。
- 事务 B 想查询?必须等事务 A 完成(提交或回滚)才能执行。
- 事务 A 完成后(比如提交,余额 500),事务 B 才能查询,读到 500 元。
-
结果:没有任何并发问题(脏读、不可重复读、幻读都不会发生),但效率极低。就像银行柜台只有一个窗口,不管多少人办理业务,都必须排队,一个办完下一个才能来 —— 安全但太慢。
一句话总结四个级别
| 隔离级别 | 通俗理解 | 核心特点 |
|---|---|---|
| 读未提交 | 能看别人没做完的事 | 问题最多,性能最好 |
| 读已提交 | 只能看别人做完的事,但可能会变 | 解决脏读,有不可重复读 / 幻读 |
| 可重复读 | 一旦看好,中间不变,结束后才更新 | 解决脏读 / 不可重复读,可能幻读 |
| 串行化 | 所有人排队,一个一个来 | 没问题,但最慢 |
这样是不是就清楚多啦?本质上,隔离级别就是 “并发事务之间互相干扰的程度”—— 级别越低,干扰越多但越快;级别越高,干扰越少但越慢。