不可重复读指同一事务内多次读取同一行数据结果不一致,因其他事务修改或删除该行;幻读指同一事务内按相同条件多次查询结果集行数变化,因其他事务插入或删除符合该条件的新行。

事务隔离级别决定了多个并发事务之间如何相互影响,其中“幻读”和“不可重复读”是两种典型的现象,它们都源于事务对数据的读取结果在执行过程中发生意外变化,但触发条件和本质不同。
什么是不可重复读
在一个事务内,多次读取同一行数据,结果不一致——比如第一次读到 age=25,第二次读变成 age=28。这通常是因为其他事务在这期间对该行执行了 UPDATE 或 DELETE 并已提交。
- 核心特征:针对同一行数据,值被修改或删除
- 发生场景:事务 A 读取某行 → 事务 B 修改/删除该行并提交 → 事务 A 再次读取,发现数据变了
- 可被 REPEATABLE READ 隔离级别解决(通过行锁或快照机制)
什么是幻读
在一个事务内,按相同条件多次查询,返回的记录数不一致——比如第一次查到 3 条 name LIKE '张%' 的记录,第二次却查到 4 条。这是因为其他事务插入(INSERT)了符合该条件的新行并已提交。
- 核心特征:针对同一查询条件,结果集行数变化(新增或消失)
- 发生场景:事务 A 查询一批数据 → 事务 B 插入/删除符合该条件的新行并提交 → 事务 A 再次查询,结果集“凭空多出”或“少掉”记录
- 仅靠行锁无法完全避免,需更高一级的控制(如间隙锁、临键锁,或升级到 SERIALIZABLE)
各隔离级别对两者的影响
不同数据库实现略有差异,以 MySQL InnoDB 为例:
- READ UNCOMMITTED:允许脏读、不可重复读、幻读
- READ COMMITTED:避免脏读,但不可重复读和幻读仍可能发生(每次 SELECT 都读最新已提交快照)
- REPEATABLE READ(InnoDB 默认):避免脏读和不可重复读;幻读在普通 SELECT 中被 MVCC 隐藏,但在 当前读(如 SELECT ... FOR UPDATE、UPDATE WHERE)下仍可能因新插入而出现
- SERIALIZABLE:通过强制串行化执行,彻底避免三者;但性能开销最大
如何应对幻读
实际开发中,不必盲目升到 SERIALIZABLE。更实用的方式包括:
- 使用 SELECT ... FOR UPDATE 或 SELECT ... LOCK IN SHARE MODE 显式加锁,配合唯一约束或业务校验
- 在应用层增加幂等性设计,例如插入前先查重,或用唯一索引拦截重复
- 合理使用 INSERT ... ON DUPLICATE KEY UPDATE 或 MERGE 类语句,减少竞态窗口
- 理解数据库的锁策略:InnoDB 的临键锁(Next-Key Lock)能同时锁住记录和间隙,是 RR 级别下防止幻读的关键机制
区分幻读与不可重复读的关键,在于关注变化的是“单行值”还是“结果集范围”。理解这一点,才能选对隔离级别和锁策略,既保障一致性,又不牺牲必要性能。










