MySQL 脏读、幻读、不可重复读是什么?

三种问题详解

上图展示了脏读的发生过程:

  • 时间点 T3:事务 B 将 id=1 的 age 从 20 改为 25,但尚未提交
  • 时间点 T4:事务 A 读取该行数据,得到 age=25
  • 时间点 T5:事务 B 回滚了,数据恢复为 20
  • 问题:事务 A 读到的 age=25 是 "脏数据",因为事务 B 根本没提交

脏读危害:基于未提交数据做业务判断,可能导致严重的逻辑错误。

上图展示了不可重复读的发生过程:

  • 时间点 T2:事务 A 第一次读取,age=20
  • 时间点 T4~T5:事务 B 修改了这条数据并提交
  • 时间点 T6:事务 A 再次读取同一行,age=25
  • 问题:同一事务内,两次读取同一数据,结果不一致

与脏读的区别:不可重复读读到的是已提交的数据,不是脏数据。

幻读的发生过程:

  • 时间点 T2:事务 A 查询 age > 20 的记录,返回 2 条
  • 时间点 T4~T5:事务 B 新增了一条 age=30 的记录并提交
  • 时间点 T6:事务 A 再次用相同条件查询,返回 3 条
  • 问题:同一事务内,两次相同查询,结果行数不同,仿佛出现了 "幻影"

与不可重复读的区别:

  • 不可重复读强调 同一条数据被修改(UPDATE/DELETE)
  • 幻读强调 结果集行数变化(INSERT/DELETE)

四种隔离级别

隔离级别详解:

  1. READ UNCOMMITTED(读未提交)

    • 最低隔离级别
    • 可能发生脏读、不可重复读、幻读
    • 几乎不使用
  2. READ COMMITTED(读已提交,RC)

    • 只能读取已提交的数据
    • 解决了脏读,但可能发生不可重复读、幻读
    • Oracle、SQL Server 默认级别
  3. REPEATABLE READ(可重复读,RR)

    • MySQL InnoDB 默认隔离级别
    • 保证同一事务内多次读取同一数据结果一致
    • 解决了脏读、不可重复读
    • InnoDB 通过 MVCC + Next-Key Lock 也解决了幻读
  4. SERIALIZABLE(串行化)

    • 最高隔离级别
    • 强制事务串行执行
    • 解决所有问题,但性能最差

三、MySQL 如何解决这些问题

MVCC 解决不可重复读:

-- RR 级别下,MVCC 保证可重复读
-- 事务 A
BEGIN;
SELECT * FROM users WHERE id = 1;  -- age=20,生成 Read View

-- 此时事务 B 修改并提交
-- UPDATE users SET age=25 WHERE id=1; COMMIT;

SELECT * FROM users WHERE id = 1;  -- 仍然读到 age=20
-- 因为 MVCC 根据事务 A 的 Read View,只能看到历史版本
COMMIT;

Next-Key Lock 解决幻读:

-- RR 级别下,当前读使用 Next-Key Lock
-- 事务 A
BEGIN;
SELECT * FROM users WHERE age > 20 FOR UPDATE;
-- 锁定 age > 20 的所有行,以及 (20, +∞) 的间隙

-- 此时事务 B 尝试插入
-- INSERT INTO users(age) VALUES(30);  -- 被阻塞!

COMMIT;  -- 事务 A 提交后,事务 B 才能插入
posted @ 2026-05-14 14:26  我用python呀  阅读(16)  评论(0)    收藏  举报