CodeGym /课程 /SQL SELF /事务执行时的异常: Dirty Read、 Non-Repeatable...

事务执行时的异常: Dirty Read、 Non-Repeatable Read、 Phantom Read

SQL SELF
第 40 级, 课程 3
可用

咱们再来复习下事务类型吧!在数据库事务的世界里,有三个主要的“反派”,能让你一天都不爽:Dirty Read、Non-Repeatable Read 和 Phantom Read。这些“异常”都是因为事务隔离级别不够高才冒出来的。今天我们就来聊聊这些反派到底是谁,它们怎么作怪,以及——最重要的——怎么对付它们。

在看例子之前,先回忆下它们都是什么意思。

  • Dirty Read(脏读):
    你读到了被修改过但还没提交(COMMIT)的数据,甚至更糟的是,这些修改可能会被回滚(ROLLBACK)。就像你给朋友转了钱,看余额发现自己破产了,但后来你反悔又把钱转回来了。魔法吧!

  • Non-Repeatable Read(不可重复读):
    你在同一个事务里两次读取同一条数据,但这期间别的事务把数据改了,你就看到两个不一样的结果。就像你看了下护照上的生日,然后把护照给朋友,他偷偷改了数字,你再看一眼,生日变了。

  • Phantom Read(幻读):
    你执行了两次同样的查询,第二次却多了几行,是别的事务新插入的。就像你数房间里有几个人,结果有人悄悄带了新朋友进来。

Dirty Read 的问题

假设我们有个表 accounts:

CREATE TABLE accounts (
    account_id SERIAL PRIMARY KEY,
    owner TEXT NOT NULL,
    balance NUMERIC(10, 2) NOT NULL
);
INSERT INTO accounts (owner, balance) VALUES ('Alice', 1000), ('Bob', 500);

事务1改了余额但还没结束,事务2同时想读这些数据。

事务1:

BEGIN;
UPDATE accounts SET balance = balance - 200 WHERE owner = 'Alice';
-- Alice 的余额变成 800,但事务还没结束。

事务2:

BEGIN;
SELECT balance FROM accounts WHERE owner = 'Alice'; -- 看到余额:800(脏读)。
ROLLBACK; -- 事务1被回滚了。

现在事务2用的是不对的数据,因为事务1把改动撤销了。要避免这种情况,可以用 READ COMMITTED 隔离级别,这样就看不到未提交的事务改动了。

Non-Repeatable Read 的问题

想象下,事务1先读了数据,别的事务把它改了,然后事务1再读一次,结果就变了。

事务1:

BEGIN;
SELECT balance FROM accounts WHERE owner = 'Bob'; -- 看到余额:500。

事务2(并行):

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE owner = 'Bob';
COMMIT;

事务1再来一次:

SELECT balance FROM accounts WHERE owner = 'Bob'; -- 看到余额:400。
COMMIT;

注意,同一个事务里数据变了。在实际场景下,比如财务报表,这种情况很要命。可以用更高的隔离级别,比如 REPEATABLE READ,来解决。

Phantom Read 的问题

假设我们有个表 orders:

CREATE TABLE orders (
    order_id SERIAL PRIMARY KEY,
    customer TEXT NOT NULL,
    total NUMERIC(10, 2) NOT NULL
);
INSERT INTO orders (customer, total) VALUES ('Alice', 100), ('Bob', 200);

事务1数订单数,别的事务插入新订单,事务1再数一次。

事务1:

BEGIN;
SELECT COUNT(*) FROM orders; -- 看到:2。

事务2(并行):

BEGIN;
INSERT INTO orders (customer, total) VALUES ('Charlie', 300);
COMMIT;

事务1再来一次:

SELECT COUNT(*) FROM orders; -- 看到:3。新的“幻影”订单出现了!
COMMIT;

要消除幻读,需要用 SERIALIZABLE 隔离级别,它会完全阻止并发改动影响结果。

怎么防止这些异常

隔离级别 vs. 异常

隔离级别 Dirty Read Non-Repeatable Read Phantom Read
Read Uncommitted ❌ 会有 ❌ 会有 ❌ 会有
Read Committed ✅ 没有 ❌ 会有 ❌ 会有
Repeatable Read ✅ 没有 ✅ 没有 ❌ 会有
Serializable ✅ 没有 ✅ 没有 ✅ 没有

怎么选对隔离级别?

  1. 如果你要快速访问数据,对异常不敏感(比如分析旧数据)——用 READ COMMITTED。
  2. 如果你想让同一个事务里数据一直稳定——用 REPEATABLE READ。
  3. 如果你要最强隔离和一致性——选 SERIALIZABLE。注意:性能可能会掉。

实用建议

用适合你场景的隔离级别来写事务。比如:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT ...;
COMMIT;

加索引,减少表锁,加快查询速度。

写高效的查询,避免长时间锁表,尤其是在 SERIALIZABLE 级别下。

常见错误和怎么避免

有时候隔离级别选错了,会导致冲突和性能下降。比如在有很多并发事务的系统里用 SERIALIZABLE,可能会锁表,事务“饿死”。

要避免这些问题,就要分析你的查询,在不同隔离级别下做性能测试,并用合适的索引。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION