脏读是事务读取到其他事务未提交且可能回滚的数据,发生在READ UNCOMMITTED级别;不可重复读是同一事务内两次读取已提交数据结果不同,发生在READ COMMITTED级别;幻读是同一事务内范围查询结果集行数变化,REPEATABLE READ下快照读可避免但当前读仍可能发生。

脏读是读到了未提交的中间状态
脏读发生在 READ UNCOMMITTED 级别,事务 A 读取了事务 B 尚未 COMMIT 的修改,而 B 后续执行 ROLLBACK,A 实际拿到的是根本不存在的数据。比如账户余额被临时改成 800,但 B 回滚后变回 1000,A 却按 800 做了后续逻辑——这已经不是“不一致”,而是“错误”。
关键点在于:脏读读的是“未提交 + 可能被撤销”的数据;它不依赖时间差,只要 B 没提交,A 就可能看到。
-
READ COMMITTED起步就杜绝脏读,因为 InnoDB 对未提交变更对其他事务不可见 - 即使在
REPEATABLE READ下,你也不会遇到脏读,这是所有高于READ UNCOMMITTED级别的底线保障 - 生产环境基本不用
READ UNCOMMITTED,除非日志类只读分析且明确接受脏数据
不可重复读是同一事务内两次读同一条记录结果不同
不可重复读出现在 READ COMMITTED 级别:事务 A 第一次查 balance = 100,事务 B 提交更新为 200,A 再次查询得到 200。两次读的都是已提交数据,但值变了——问题不在“脏”,而在“事务内读取结果不稳定”。
它和脏读的本质区别是:不可重复读读到的始终是合法、已落地的数据,只是这个“落地”发生在 A 的两次查询之间。
-
REPEATABLE READ通过 MVCC 快照解决这个问题:A 启动时生成一致性视图,后续所有普通SELECT都基于该快照,B 的提交不影响 A 的读结果 - 注意:
SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE是当前读,不受快照保护,仍可能看到新提交值 - 不可重复读只影响单行精确查询,范围查询(如
WHERE age > 18)在REPEATABLE READ下还可能面临幻读
隔离级别选错会直接暴露并发漏洞
不是“越高越安全”,而是每级都在释放不同锁、生成不同快照、允许不同可见性。选错意味着要么锁太多拖垮写吞吐,要么漏太多导致业务逻辑崩坏。
-
READ COMMITTED:每次SELECT都拉最新快照,适合高并发写场景(如订单流水),但需接受事务内读不一致 -
REPEATABLE READ:默认值,平衡较好,但更新时用到间隙锁(Gap Lock),容易因插入冲突引发死锁,尤其在INSERT ... SELECT或唯一键冲突重试时 -
SERIALIZABLE:所有SELECT隐式加LOCK IN SHARE MODE,读写都排队,小流量验证逻辑可以,线上服务基本扛不住 - 全局改隔离级别用
SET GLOBAL TRANSACTION ISOLATION LEVEL ...,但已有连接不受影响;会话级用SET SESSION TRANSACTION ISOLATION LEVEL ...
幻读常被误认为不可重复读,但它针对的是行数变化
不可重复读关注“某条记录的值变了”,幻读关注“符合查询条件的行数变了”。比如事务 A 查 name = '张三' 有 3 条,B 插入第 4 条并提交,A 再查发现 4 条——这就是幻读。
很多人以为 REPEATABLE READ 完全解决幻读,其实不然:MVCC 快照读确实让 A 两次查都看到 3 条(InnoDB 默认行为),但若 A 执行 SELECT ... FOR UPDATE,InnoDB 会加 next-key lock,阻止 B 插入,这才真正抑制幻读。
- 幻读只在范围查询或存在索引间隙时发生,单行主键查询不会出现
- 如果你的业务依赖“查无此记录 → 插入”,又没加锁,
REPEATABLE READ下仍可能因幻读触发唯一键冲突 - 真正要堵死幻读,得结合
SELECT ... FOR UPDATE或业务层幂等判断,不能只靠隔离级别











