我們再來複習一下交易的類型吧!在資料庫交易的世界裡,有三個主要的「壞蛋」會讓你很頭痛:Dirty Read、Non-Repeatable Read 跟 Phantom Read。這些「異常」都是因為交易隔離等級不夠高才會出現。今天就來聊聊這些壞蛋是誰、怎麼出現的,還有——最重要的——怎麼對付它們。
在看例子之前,先來回憶一下這些異常是什麼意思。
Dirty Read(髒讀):
你讀到的資料已經被改過,但那個改資料的交易還沒 commit(COMMIT),甚至可能會被 rollback(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 隔離等級來避免,這樣就看不到還沒 commit 的改動了。
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 |
✅ 不會 | ✅ 不會 | ✅ 不會 |
怎麼選對隔離等級?
- 如果你要的是快取資料,異常沒那麼重要(比如分析舊資料)——用
READ COMMITTED。 - 如果你要同一個交易裡資料都穩定——用
REPEATABLE READ。 - 如果你要最嚴格的隔離和一致性——選
SERIALIZABLE。記得:效能可能會變差喔。
實用建議
根據你的需求選對交易隔離等級。例如:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT ...;
COMMIT;
加上 index,可以減少 table lock,加快查詢速度。
用優化過的查詢,避免長時間鎖表,尤其是在 SERIALIZABLE 隔離等級下。
常見錯誤跟怎麼避免
有時候隔離等級選錯會造成衝突或效能下降。比如在一個有很多並行交易的系統用 SERIALIZABLE,可能會造成鎖死或交易「飢餓」。
要避免這種情況,記得分析你的查詢,在不同隔離等級下測試效能,還有用對的 index。
GO TO FULL VERSION