数据库的脏读、不可重复读和幻读分别是什么?
- 标签:后端、MySQL、数据库
🏛 业务架构师
这三个问题本质是并发事务之间的相互干扰,我用业务后果来讲最清楚:
- 脏读(读未提交):事务 A 读到了 B 尚未提交的数据,B 一回滚,A 就基于一个从未存在过的值做了决策。比如风控读到一笔未提交的异常转账就放行了下游贷款,回滚后资损已经发生;
- 不可重复读(前后不一致):同一事务内两次读同一行结果不同(别人提交了 update)。报表事务里第一次读余额 100 万、第二次 80 万,对账口径混乱;
- 幻读(行数变化):同一事务两次按条件查询,结果集行数变了(别人 insert/delete)。典型是"查询没有重复记录就插入"的判重逻辑,两次查询之间被并发插入了重复数据——优惠券重复发放就是这类事故。
业务上我的处理原则分两档:
- 数据正确性直接关联资损的场景(支付、库存、账户),不能指望隔离级别兜底,要上悲观锁
select for update、乐观锁版本号或唯一约束,把并发安全建立在业务规则上; - 一般场景靠隔离级别 + 幂等设计,MySQL 默认 RR 已覆盖不可重复读,幻读靠间隙锁或业务唯一索引解决。
隔离级别越高并发越低,这是一致性与吞吐的定价问题,按业务风险分级投入才是架构师该做的。
🏗 系统架构师
这三个是事务隔离级别要解决的三类读异常,我按严重程度递进讲:
- 脏读:事务 A 读到了 B 未提交的数据,B 一回滚 A 就基于脏数据做了决策,等于业务建立在可能不存在的事实上;
- 不可重复读:A 两次读同一行,中间 B 提交了 update,两次结果不一致,破坏的是"读的一致性视图",影响同一事务内的校验逻辑(如读余额→扣款);
- 幻读:A 两次范围查询,中间 B insert 了新行,多出了"幻影行",破坏的是集合层面的一致性。
对应关系:读未提交三种都可能发生;读已提交解决脏读;可重复读(RR)再解决不可重复读,InnoDB 在 RR 下靠 MVCC + next-key lock 基本消除幻读;串行化全部解决但吞吐代价大。
我的实践:大多数交易核心用 RR 保语义,统计报表类走 RC 加乐观重试,用隔离级别的降级换并发度,前提是业务能容忍对应的读异常。
💻 全栈开发专家
三种并发读问题,按严重程度递进:
- 脏读:事务 A 读到了事务 B 未提交的数据,B 一回滚,A 拿的就是脏数据。RC 及以上级别解决。
- 不可重复读:事务 A 两次读同一行,中间 B 改了并提交,A 两次结果不一样。针对修改,RR 级别解决。
- 幻读:事务 A 两次范围查询,中间 B 插入了新行,A 第二次多出"幻影行"。针对插入/删除。
MySQL 的 RR 靠 **MVCC + next-key 锁(记录锁 + 间隙锁)**解决幻读:快照读靠 MVCC 读旧版本;当前读(SELECT ... FOR UPDATE)靠间隙锁挡住区间内插入。
工程提醒:RC 级别下间隙锁基本失效,FOR UPDATE 只锁命中的行。我遇过一个排查案例——代码以为锁住了范围,实际隔离级别是 RC(公司统一配置),并发还是插入了重复数据,最后靠唯一索引兜底。所以并发防重的最后防线永远是无锁的唯一索引,而不是依赖锁语义。