一、脏读(Dirty Read)
产生原因
-
直接原因:事务 A 读取了事务 B 未提交的修改,而事务 B 后续可能回滚(Rollback),导致事务 A 读取到无效数据。
-
根本原因:事务隔离级别设置为 Read Uncommitted(读未提交),允许事务读取其他未提交事务的中间状态。
场景示例
-
事务 B 更新某行数据(但未提交)。
-
事务 A 读取该行数据(未提交的脏数据)。
-
事务 B 回滚,数据恢复原值。
-
事务 A 后续操作基于脏数据,导致逻辑错误。
二、不可重复读(Non-Repeatable Read)
产生原因
-
直接原因:事务 A 多次读取同一数据,期间事务 B 对该数据进行了 修改并提交,导致事务 A 前后读取结果不一致。
-
根本原因:事务隔离级别为 Read Committed(读已提交),允许其他事务在两次读取之间提交修改。
场景示例
-
事务 A 第一次读取某行数据,值为 100。
-
事务 B 更新该行数据为 200 并提交。
-
事务 A 第二次读取同一行数据,值为 200。
-
事务 A 在同一事务中两次读取结果不一致,破坏事务的隔离性。
三、幻读(Phantom Read)
产生原因
-
直接原因:事务 A 多次执行同一范围查询,期间事务 B 插入或删除了符合该查询条件的记录并提交,导致事务 A 前后读取的结果集大小不一致。
-
根本原因:事务隔离级别为 Repeatable Read(可重复读)时,MVCC 的快照读可以避免幻读,但 当前读(如
SELECT ... FOR UPDATE
)可能因未锁定范围而出现幻读。-
InnoDB 的优化:通过 Next-Key Lock(记录锁 + 间隙锁)锁定索引范围,防止其他事务插入数据。
-
场景示例
-
事务 A 查询年龄在 20-30 岁的用户,返回 5 条记录。
-
事务 B 插入一条年龄为 25 岁的新用户并提交。
-
事务 A 再次查询同一范围,返回 6 条记录。
-
事务 A 发现“凭空出现”的新数据,如同幻觉(幻读)。
-
四、三者的核心区别
问题类型 操作类型 数据变化形式 隔离级别场景 脏读 读取未提交的数据 单行数据的未提交修改 Read Uncommitted 不可重复读 读取已提交的数据 单行数据的已提交修改 Read Committed 幻读 范围查询 结果集中记录的增删 Repeatable Read(未完全解决)
五、数据库如何解决这些问题?
-
脏读:提升隔离级别至 Read Committed(读已提交),确保只读取已提交的数据。
-
不可重复读:提升隔离级别至 Repeatable Read(可重复读),通过 MVCC 的快照保证事务内一致性。
-
幻读:在 Repeatable Read (可重复读)下,通过 Next-Key Lock(记录锁+间隙锁) 锁定索引范围;或提升至 Serializable(串行化) 完全串行化执行。