✅ 并发事务三大“读问题”对比总结
问题类型 | 定义(重点记忆) | 触发场景(典型案例) | MVCC 能解决吗 | 需加锁吗 | 哪些隔离级别会出现(MySQL InnoDB) |
---|---|---|---|---|---|
脏读 | 读取了其他事务尚未提交的数据,对方可能会回滚 | A 修改数据未提交,B 读取到了修改后数据 | ❌ 否 | ❌ 否 | ✅ Read Uncommitted ❌ RC / RR / Serializable 不会出现 |
不可重复读 | 同一条记录在同一事务中读两次,前后值不一致(值变了) | A 开始事务,读一次;B 提交修改;A 再读一次发现数据变了 | ✅ 是 | ❌ 否 | ✅ Read Committed ❌ Repeatable Read / Serializable |
幻读 | 同一查询条件,在同一事务中结果行数不同,新增/删除数据带来的“幻觉” | A 查询“年龄>18的用户”;B 插入新的满足条件用户;A 再查时发现多了/少了行 | ❌ 否 | ✅ 是 | ✅ Read Committed 会出现 ❌ Repeatable Read 用间隙锁解决 ❌ Serializable 用加锁彻底杜绝 |
🧪 场景演示案例
1️⃣ 脏读(Read Uncommitted 级别才有)
👇 示例步骤:
时间点 | 事务 A | 事务 B |
---|---|---|
T1 | BEGIN; UPDATE user SET age=30 WHERE id=1; (未提交) | |
T2 | SELECT age FROM user WHERE id=1; (读到了 30) | |
T3 | ROLLBACK; |
💥 事务 B 读到了 A 未提交就回滚的数据 → 就是“脏读”。
2️⃣ 不可重复读(RC 级别会发生)
👇 示例步骤:
时间点 | 事务 A | 事务 B |
---|---|---|
T1 | BEGIN; SELECT age FROM user WHERE id=1; (读出 age = 25) | |
T2 | UPDATE user SET age=30 WHERE id=1; COMMIT; | |
T3 | SELECT age FROM user WHERE id=1; (读出 age = 30) |
💥 同一事务两次读取,值不同 → 就是“不可重复读”。
✅ MVCC 会提供快照版本,使事务 A 始终看到第一次读到的值 → 避免这个问题(RR 级别中有效)。
3️⃣ 幻读(范围查询的新增/删除)
👇 示例步骤:
时间点 | 事务 A | 事务 B |
---|---|---|
T1 | BEGIN; SELECT * FROM user WHERE age > 20; (查出2条) | |
T2 | INSERT INTO user(age=25); COMMIT; | |
T3 | SELECT * FROM user WHERE age > 20; (查出3条!) |
💥 同一查询条件,结果行数变了 → 幻读。
🔒 InnoDB 使用“间隙锁(Gap Lock)”避免 RR 中出现幻读。
🎯 面试说辞模板(直接背):
并发读问题主要包括脏读、不可重复读和幻读:
脏读指事务读到了其他事务未提交的数据,只会出现在 Read Uncommitted 中;
不可重复读是同一事务中两次读取同一数据结果不同,MVCC 可以解决;
幻读是范围查询时记录新增/删除带来的行数差异,MVCC 无法解决,需要通过间隙锁或 Serializable 加锁。
MySQL InnoDB 默认隔离级别是 Repeatable Read,能避免前两种问题,对幻读则通过间隙锁防止。
✅ 拓展补充:MVCC 的角色
问题类型 | MVCC 是否可解决 | 原因说明 |
---|---|---|
脏读 | ❌ 不可解决 | 本质是读到了未提交数据,MVCC 不能保证提交前版本 |
不可重复读 | ✅ 可解决 | 同一事务读同一快照版本 |
幻读 | ❌ 不可解决 | MVCC 针对行数据快照,不能控制查询结果集范围 |
✅ 实战答题模板(简明逻辑)
在数据库并发控制中,事务的隔离级别影响对三大并发问题(脏读、不可重复读、幻读)的处理方式:
Read Uncommitted 存在所有问题;
Read Committed 避免脏读,但不可重复读和幻读仍会发生;
Repeatable Read 是 MySQL 默认级别,结合 MVCC 可避免不可重复读,同时通过间隙锁防止幻读;
Serializable 使用最强锁机制,通过串行化避免一切并发异常,但性能损耗最大。
因此在实际项目中,MySQL 多使用 RR 隔离级别 + MVCC,兼顾性能与一致性。