1. 引言
在数据库并发访问场景中,多个事务同时读写同一份数据时,如果没有合理的隔离机制,就可能出现脏读、不可重复读和幻读等问题。理解这三种异常现象及其解决方案,是掌握数据库事务隔离级别的基础。
2. 脏读(Dirty Read)
脏读是指一个事务读取到了另一个事务尚未提交的数据。如果那个事务最终回滚,那么读取到的数据就是"脏"的、不真实的。
举例说明:
- 事务 A 将某账户余额从 100 元修改为 200 元,但尚未提交。
- 事务 B 此时读取该账户余额,读到 200 元。
- 随后事务 A 回滚,余额恢复为 100 元。
- 事务 B 基于 200 元做出的业务判断就是错误的,这就是脏读。
解决方案 :将隔离级别设置为**读已提交(Read Committed)**或更高,即可避免脏读。读已提交保证事务只能读取到其他事务已提交的数据。
3. 不可重复读(Non-Repeatable Read)
不可重复读是指在一个事务内,两次读取同一行数据,却得到了不同的结果。原因是两次读取之间,该行数据被其他事务修改并提交了。
举例说明:
- 事务 A 第一次读取某商品价格为 50 元。
- 事务 B 将该商品价格修改为 80 元并提交。
- 事务 A 第二次读取同一商品,价格变为 80 元。
- 事务 A 内两次读取结果不一致,这就是不可重复读。
解决方案 :将隔离级别设置为**可重复读(Repeatable Read)**或更高。可重复读通过行级锁或快照机制,保证事务内多次读取同一行数据结果一致。
4. 幻读(Phantom Read)
幻读是指在一个事务内,两次执行相同的查询 ,第二次却返回了第一次没有出现的行。原因是两次查询之间,其他事务插入了新的数据行并提交。
举例说明:
- 事务 A 查询价格大于 100 元的商品,返回 3 行。
- 事务 B 插入一条价格为 150 元的新商品并提交。
- 事务 A 再次执行相同查询,返回 4 行。
- 多出来的那一行就像"幻影"一样出现,这就是幻读。
解决方案 :将隔离级别设置为串行化(Serializable) ,或使用**间隙锁(Gap Lock)/ 临键锁(Next-Key Lock)**来锁定查询范围,阻止其他事务在范围内插入新行。
5. 四种隔离级别与问题对照
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交(Read Uncommitted) | 可能 | 可能 | 可能 |
| 读已提交(Read Committed) | 不会 | 可能 | 可能 |
| 可重复读(Repeatable Read) | 不会 | 不会 | 可能(MySQL 默认级别下通过间隙锁可避免) |
| 串行化(Serializable) | 不会 | 不会 | 不会 |
6. 总结
脏读、不可重复读和幻读是数据库并发控制中的三类典型异常,分别对应未提交数据、已提交行数据变更和新增数据行三种场景。隔离级别越高,并发性能越低,实际生产中应根据业务对数据一致性的要求,在正确性和性能之间做出权衡。