脏读、不可重复读、幻读是事务并发下因隔离不足导致的三类问题:脏读读取未提交回滚数据;不可重复读是同一行两次读取值不同;幻读是同一范围查询结果集行数变化。

脏读、不可重复读、幻读是数据库事务并发控制中的三类典型问题,本质都源于多个事务同时操作同一数据时缺乏合理隔离。它们不是“bug”,而是不同隔离级别下允许或禁止的现象,理解关键在于搞清每种现象发生的条件和对应的隔离级别设计逻辑。
脏读:读到了未提交的“垃圾数据”
一个事务读取了另一个事务尚未提交的修改,而后者随后回滚,导致前者读到的数据在数据库中从未真实存在过。
例如:事务A将某账户余额从100改为200但未提交;事务B此时读取该账户余额为200;事务A回滚,余额恢复为100;事务B已基于错误的200做了后续计算——这就是脏读。
解决方式:读已提交(Read Committed)及以上隔离级别即可避免脏读。数据库通过行级锁或MVCC机制,确保只读取已成功提交的数据版本。
不可重复读:同一条记录,两次读出来不一样
在同一事务内,对同一行数据执行两次相同查询,结果不一致。原因通常是其他事务在两次读之间对该行进行了修改并提交。
例如:事务A第一次读取某订单状态为“待支付”;事务B更新该订单为“已支付”并提交;事务A再次读取,发现状态变成“已支付”。两次读结果不同,但都是已提交的有效数据。
注意:这和脏读不同,它读的是已提交数据,只是“不一致”。可重复读(Repeatable Read)及以上级别能防止不可重复读,通常通过快照读(如MySQL InnoDB的MVCC)实现——事务启动时建立数据快照,后续读均基于该快照。
幻读:同个范围查询,两次查出的行数不同
在同一事务中,执行相同条件的范围查询(如SELECT * FROM orders WHERE status='已支付'),第二次查询返回了第一次没有的新行(或少了某些行),就像出现了“幻影”。根本原因是其他事务在此期间插入(或删除)了符合查询条件的新记录并提交。
例如:事务A查询所有已支付订单共5条;事务B插入一条新已支付订单并提交;事务A再次执行同样查询,返回6条——多出的一条就是幻行。
仅串行化(Serializable)能完全避免幻读;部分数据库(如MySQL InnoDB)在可重复读级别下通过间隙锁(Gap Lock)+ Next-Key Lock 抑制幻读,但严格来说,标准SQL定义中可重复读并不保证无幻读,需结合具体实现理解。
隔离级别与问题对应关系简表
(以SQL标准为基础,实际行为依数据库实现略有差异)
- 读未提交(Read Uncommitted):脏读、不可重复读、幻读都可能发生
- 读已提交(Read Committed):避免脏读;仍可能发生不可重复读和幻读
- 可重复读(Repeatable Read):避免脏读和不可重复读;是否避免幻读取决于实现(InnoDB基本避免,PostgreSQL MVCC下仍可能)
- 串行化(Serializable):三者全部避免,但性能开销最大,通常通过加范围锁或序列化调度实现
选隔离级别不是越高越好,而是在数据一致性要求和系统并发性能之间权衡。大多数业务场景用读已提交足够;金融强一致性场景可考虑可重复读;真正需要串行化的场景极少,往往说明架构或设计有优化空间。










