0Pricing
SQL Interview Prep · 课时

脏读、不可重复读与幻读

了解三种读取异常,以及哪种隔离级别可以阻止它们。

脏读、不可重复读与幻读 是 CoddyKit 上的免费 SQL Interview Prep 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 SQL Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 SQL Interview Prep 课程共包含 4 节课。

三种读取异常

隔离级别用于防止特定的并发错误,这些错误称为读取异常。面试官希望您能够准确定义这三种异常,并将每种异常对应到能够阻止它的级别。

  • 脏读 - 读取未提交的数据
  • 不可重复读 - 两次读取之间某一行发生变化
  • 幻读 - 两次读取之间出现了新行

难点在于区分不可重复读和幻读,因为两者都涉及重新查询并得到不同的结果。

脏读:定义

脏读发生在事务 T1 读取了事务 T2 已修改但尚未提交的行时。如果 T2 随后回滚,T1 就基于从未真正存在过的数据执行了操作。

只有 READ UNCOMMITTED 允许脏读。所有更高的隔离级别都会禁止脏读。

现实中的风险:根据一笔几秒后被回滚的存款批准贷款。

脏读:时间线

请将两列按时间线阅读。T1 在 READ UNCOMMITTED 下运行。

T1 看到余额为 700,但 T2 从未提交。这个 700 是 T2 尚未完成的工作产生的幻象。T2 回滚后,真实值仍为 500。T1 基于垃圾数据做出了决定。

-- T2 (not committed)        | -- T1 (READ UNCOMMITTED)
BEGIN;                       |
UPDATE accounts              |
  SET balance = 700          |
  WHERE id = 1;              |
                             | SELECT balance FROM accounts
                             |   WHERE id = 1;  -- reads 700 (dirty!)
ROLLBACK;                    |
                             | -- T1 acted on a value that never existed

不可重复读:定义

不可重复读发生在 T1 读取某一行后,T2 对同一行执行提交了的更新或删除,而 T1 再次读取时看到不同的值。

请注意它与脏读的关键区别:这里 T2 已经提交。数据是真实的,但在同一个事务期间,它在 T1 的操作过程中发生了变化。

READ COMMITTED 仍然允许这种情况。REPEATABLE READ 及更高的级别通过读取稳定的快照来防止它。

不可重复读:时间线

T1 在 READ COMMITTED 下运行,并两次读取同一行。在两次读取之间,T2 提交了一项更改。

同一个主键在一个事务中返回了两个不同的值。这种不一致会破坏那些假设该行保持稳定的多步骤逻辑。

-- T1 (READ COMMITTED)              | -- T2
BEGIN;                              |
SELECT balance FROM accounts        |
  WHERE id = 1;  -- 500            |
                                    | BEGIN;
                                    | UPDATE accounts SET balance = 900
                                    |   WHERE id = 1;
                                    | COMMIT;
SELECT balance FROM accounts        |
  WHERE id = 1;  -- 900 (changed!) |
COMMIT;                             |

幻读:定义

幻读发生在 T1 执行带有搜索条件的查询后,T2 提交了对匹配该条件的行执行的 INSERT(或 DELETE),而 T1 重新运行查询时看到了不同的行集合。

它与不可重复读的区别在于:不可重复读关注的是现有行的值发生变化;幻读关注的是匹配某个谓词的行数发生变化。

按照标准,只有 SERIALIZABLE 能够保证防止幻读。

幻读:时间线

T1 两次统计高价值账户的数量。在两次统计之间,T2 插入了一条符合条件的新行并提交。

没有任何现有行发生变化,但 COUNT 却不同。新行就是出现在 T1 结果集中的“幻影”。

-- T1 (REPEATABLE READ, standard)      | -- T2
BEGIN;                                 |
SELECT COUNT(*) FROM accounts           |
  WHERE balance > 1000;  -- 3          |
                                       | INSERT INTO accounts(id, balance)
                                       |   VALUES (99, 5000);
                                       | COMMIT;
SELECT COUNT(*) FROM accounts           |
  WHERE balance > 1000;  -- 4 (phantom)|
COMMIT;                                |

将异常映射到隔离级别

这种映射是本主题的核心。能够防止每种异常的最低级别如下:

  • 脏读从 READ COMMITTED 开始被防止。
  • 不可重复读从 REPEATABLE READ 开始被防止。
  • 幻读由 SERIALIZABLE 防止(依据标准)。

请注意这些名称是对应的:REPEATABLE READ 让读取变得可重复;这些级别的名称取自它们新解决的异常。

不可重复读与幻读:明确界线

这是面试中最常见的混淆点。请记住下面这句话:

不可重复读 = 现有行的值发生了变化。幻读 = 匹配的行集合发生了变化(增加或删除了行)。

请自行测试:T2 执行 UPDATE ... WHERE id = 5 后提交,T1 重新读取第 5 行。这是不可重复读。T2 执行 INSERT,插入一条与 T1 的 WHERE 匹配的新行,T1 重新运行查询。这是幻读。

写偏斜:额外的异常

高级面试可能会超出三种标准异常,考察写偏斜:两个事务各自读取一个有重叠的集合,根据读取结果执行互不相交的写入,随后都提交,导致最终状态是任何一个事务单独执行时都不会允许的状态。

经典例子是:两位医生值班;每位医生都确认另一位医生在岗,然后各自离开值班岗位。两次操作都成功,最终没有任何医生值班。

快照隔离(Postgres 的 REPEATABLE READ)允许写偏斜;只有 SERIALIZABLE 能够阻止它。提到这一点能体现理解的深度。

丢失更新:第四个陷阱

面试官有时会加入丢失更新,它不在标准的异常列表中,但在实践中非常常见。两个事务读取同一个值,都根据该值计算出新值,然后都写回结果。第二次写入会悄无声息地覆盖第一次写入。

例如:两笔转账都读取余额 500,各自扣除一笔金额,然后各自写入计算结果。其中一次扣除会丢失。

解决方案不只是提高隔离级别,还包括使用 SELECT ... FOR UPDATE 进行显式锁定,或者执行原子更新,让数据库而不是应用程序来计算结果。

-- Safe pattern: lock the row, or compute atomically
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;  -- locks row
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
-- Or simply: UPDATE accounts SET balance = balance - 100 WHERE id = 1;

快速检查

请根据行为识别异常类型。

总结:异常及其解决方案

三种读取异常,每种都可以通过更高的隔离级别解决:

  • 脏读(未提交的数据) - 在 READ COMMITTED 级别得到解决。
  • 不可重复读(现有行的值发生变化) - 在 REPEATABLE READ 级别得到解决。
  • 幻读(匹配的行集合发生变化) - 在 SERIALIZABLE 级别得到解决。

请牢记不可重复读和幻读之间的明确界线;如果面试官想听更多,再提到写偏斜。接下来我们将了解数据库引擎实际上如何实现隔离:锁定、死锁和 MVCC。

常见问题解答

「脏读、不可重复读与幻读」课时是免费的吗?

是的 — 「脏读、不可重复读与幻读」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 SQL Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 SQL Interview Prep 课程共包含 4 节课。

「脏读、不可重复读与幻读」这节课中我会学到什么?

了解三种读取异常,以及哪种隔离级别可以阻止它们。 你通过在浏览器中直接运行的动手代码来练习 SQL Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 SQL Interview Prep 需要有经验吗?

无需任何先前经验。CoddyKit 上的 SQL Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「脏读、不可重复读与幻读」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 SQL Interview Prep 课中编写并运行代码吗?

能。每节 SQL Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. ACID 属性详解
  2. 四种隔离级别
  3. 脏读、不可重复读与幻读
  4. 死锁、锁与 MVCC
← 返回 SQL Interview Prep