不可重复读是同一行数据的值被修改,幻读是相同条件查询的行数变化;前者由UPDATE/DELETE引起,后者由INSERT/DELETE引起;RR级别下MVCC解决不可重复读,临键锁缓解幻读。

幻读和不可重复读都是事务并发时出现的一致性问题,但它们发生的场景、影响对象和底层机制完全不同。
核心区别:改的是“值”还是“行数”
不可重复读关注的是同一行数据的值被修改。比如事务A第一次查到用户余额是100元,事务B随后把它改成200元并提交,事务A再查就变成200元——数据内容变了,但还是那一行。
幻读关注的是相同条件查询返回的行数变了。比如事务A第一次查 age > 25 的用户有3条,事务B插入一条 age=28 的新记录并提交,事务A再查就变成4条——多出了一行“看不见”的数据,像幻觉一样。
- 不可重复读:行存在,值变了(UPDATE/DELETE 引起)
- 幻读:行“凭空出现”或“突然消失”(INSERT/DELETE 引起)
触发条件与典型场景
不可重复读常见于基于主键或唯一索引的精确查询。例如:SELECT * FROM users WHERE id = 123,两次执行结果中 name 字段不一致。
幻读则多发生在范围查询或无主键约束的业务校验中。例如:SELECT COUNT(*) FROM orders WHERE status = 'pending',两次统计结果不同;或者先查“不存在订单号为ORD999的记录”,再插入同号订单却报主键/唯一键冲突。
- 不可重复读破坏的是单行数据的逻辑稳定性(如扣款前后的余额校验)
- 幻读破坏的是结果集的整体语义(如分页总数不准、批量处理漏数据、乐观锁重试失败)
MySQL InnoDB 中的应对机制
在默认的可重复读(RR)隔离级别下:
- 不可重复读已被 MVCC 解决:事务启动时生成 Read View,后续所有 SELECT 都基于该快照,看到的始终是事务开始时刻的数据版本
- 幻读未被完全消除:MVCC 不作用于新插入的行,所以范围查询仍可能看到其他事务新提交的记录
- InnoDB 用临键锁(Next-Key Lock)缓解幻读:对查询涉及的索引范围加锁(记录锁 + 间隙锁),阻止其他事务在该范围内插入
- 若需彻底避免幻读,可升级到串行化(SERIALIZABLE),或在关键语句后显式加锁,如
SELECT ... FOR UPDATE
一句话总结
不可重复读是“我读的那条记录变样了”,幻读是“我查的这个范围多出来(或少掉)了一条记录”。前者靠 MVCC 拦住,后者得靠锁来兜底。










