在数据库事务中,脏读、不可重复读和幻读是三种常见的并发问题。它们描述了当多个事务同时执行时,由于隔离性不足可能导致的数据不一致现象。
1. 脏读
定义:一个事务读取了另一个尚未提交的事务修改的数据。如果那个事务最终回滚了,那么当前事务读到的数据就是“脏”的、不存在的。
场景例子:
假设有一个银行转账系统,事务A(转出)和事务B(查看余额)并发执行。
- 事务A:开始,把账户余额从 1000 元改为 800 元(此时尚未提交)。
- 事务B:开始,读取余额,看到的是 800 元。
- 事务A:发生错误,执行回滚,余额恢复为 1000 元。
后果:
事务B 读到的 800 元是根本不存在的“脏数据”。如果事务B 基于这个 800 元做决策(例如判断余额不足而拒绝交易),就会产生业务逻辑错误。
解决方案:
将数据库隔离级别设置为 “读已提交” 或更高。在该级别下,事务B 必须等待事务A 提交后,才能读取到修改后的数据。
2. 不可重复读
定义:在同一个事务内,多次读取同一行数据,得到的结果不一致。这是因为在两次读取之间,另一个事务修改了这一行数据并提交了。
注意:不可重复读关注的是对同一条数据的修改。
场景例子:
假设事务A 需要统计某商品的两次价格快照。
- 事务A:开始,第一次读取商品X 的价格,显示为 100 元。
- 事务B:开始,修改商品X 的价格从 100 元改为 80 元,并提交。
- 事务A:再次读取商品X 的价格,显示为 80 元。
后果:
在同一个事务A 内部,前后两次读取同一行数据结果不同(100 -> 80)。如果事务A 是基于第一次读取的结果进行业务操作(例如按 100 元计算税费),随后发现数据变了,会导致逻辑混乱。
解决方案:
将数据库隔离级别设置为 “可重复读” 或更高。在该级别下,事务A 在第一次读取后,数据库会通过机制(如快照读)保证后续读取该行数据时,看到的依然是第一次读取时的快照,从而解决不可重复读。
3. 幻读
定义:在同一个事务内,多次执行同一个查询(带有范围条件),返回的结果集数量不一致。这是因为在两次查询之间,另一个事务插入或删除了符合条件的新行并提交了。
注意:幻读关注的是数据集合的新增或删除,而不是对已有数据的修改。
场景例子:
假设事务A 需要统计用户表中的所有男性用户。
- 事务A:开始,执行
SELECT * FROM users WHERE gender = 'male',返回了 10 条记录。 - 事务B:开始,插入了一条新的男性用户记录,并提交。
- 事务A:再次执行相同的查询,返回了 11 条记录。
后果:
事务A 看到了之前不存在的“幻影”行。如果事务A 的目的是基于第一次的统计结果进行全量操作(例如给所有男性用户发优惠券),突然多出的一行会导致操作遗漏或重复。
解决方案:
将数据库隔离级别设置为 “串行化”。在串行化级别下,事务A 在查询范围内会加上范围锁,阻止其他事务插入或删除符合条件的数据。
注:在 MySQL 的 InnoDB 引擎中,“可重复读” 级别通过 Next-Key Lock(间隙锁+行锁) 机制在一定程度上也解决了幻读问题(针对当前读),但在理论上,标准的“可重复读”级别是不防幻读的。
三者对比总结
| 特性 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 关注点 | 读取了未提交的修改 | 同一条记录内容变了 | 结果集的行数变了 |
| 操作类型 | 主要是 UPDATE (修改) | 主要是 UPDATE (修改) | 主要是 INSERT / DELETE (插入/删除) |
| 发生条件 | 读到了事务回滚前的数据 | 两次读取之间,数据被修改并提交 | 两次查询之间,数据被增删并提交 |
| 隔离级别解决 | 读已提交 及以上 | 可重复读 及以上 | 串行化 (或依靠特定数据库的锁机制) |
延伸:数据库隔离级别
SQL 标准定义了四种隔离级别,它们与上述问题的关系如下:
- 读未提交:可能出现 脏读、不可重复读、幻读。
- 读已提交:解决脏读,可能出现 不可重复读、幻读(Oracle、SQL Server 默认级别)。
- 可重复读:解决脏读和不可重复读,理论上可能出现 幻读(MySQL InnoDB 默认级别,但通过 Next-Key Lock 基本解决了幻读)。
- 串行化:解决所有问题(通过强制事务串行执行,性能最低)。
简单记忆法:
- 脏读:别人还没确定要这么做(未提交),你就信了。
- 不可重复读:同一行数据,两次看不一样了。
- 幻读:同一波数据,两次数数量不一样了。

6万+

被折叠的 条评论
为什么被折叠?



