我們來搞懂一下 READ COMMITTED 這個隔離等級到底在幹嘛。它的名字就有點像在說,所有你在 transaction 裡讀到的東西,都是別的 transaction 已經「commit」過的。就像現實生活中,我們只相信那些已經正式記錄在案的八卦一樣。
認真說,這個等級保證你不會看到其他 transaction 還沒 commit 的變更。這就解決了「dirty read」(Dirty Read)的問題。但要注意,你讀到的資料還是有可能被別的 transaction 在你兩次查詢之間 commit 掉,這就會有「non-repeatable read」(Non-Repeatable Read)的風險。
在 PostgreSQL 裡,READ COMMITTED 是預設的隔離等級。就像預設模式一樣,你不用特別設定就會用到它。
隔離等級 READ COMMITTED 的使用範例
我們來看個實際例子。假設有一張 accounts 表,裡面放著用戶跟他們的餘額:
CREATE TABLE accounts (
account_id SERIAL PRIMARY KEY,
account_name TEXT NOT NULL,
balance NUMERIC(10, 2) NOT NULL
);
INSERT INTO accounts (account_name, balance)
VALUES ('Alice', 1000.00), ('Bob', 500.00);
現在想像一下這個情境。Session 1 跟 Session 2 是我們故事裡的兩個主角,他們都在操作 accounts 這張表。流程大概是這樣:
Session 1:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_name = 'Alice';
-- 還沒 COMMIT 或 ROLLBACK。
這時候 Alice 的 balance 暫時被扣了 100,但結果還沒真正寫進資料庫。
Session 2:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SELECT balance FROM accounts WHERE account_name = 'Alice';
結果:Session 2 看到 Alice 的餘額還是 1000.00,因為 Session 1 的 transaction 還沒結束(還沒 COMMIT)。READ COMMITTED 這個等級幫我們擋掉了「dirty read」。
Session 1 結束 transaction:
COMMIT;
現在 Alice 的餘額更新了,資料庫裡已經記下 900.00。
Session 2 再查一次:
SELECT balance FROM accounts WHERE account_name = 'Alice';
結果:這次 Session 2 看到 Alice 的新餘額是 900.00。注意,這跟上次查詢結果 1000.00 不一樣。這就是「non-repeatable read」的問題。
什麼時候該用 READ COMMITTED?
READ COMMITTED 這個隔離等級就是在效能跟一致性之間抓個平衡。有幾種情境它超適合:
簡單的 CRUD 操作: 你只是單純查資料或更新資料,沒什麼複雜關聯。
批次更新: 比如你要一次更新很多資料,想馬上看到已經 commit 的變更。
交易處理: 像支付系統,讓用戶只能看到已經確認過的資料。
但如果你要做很複雜的分析查詢,或是資料量很大,可能要考慮用別的隔離等級,比如 REPEATABLE READ。
READ COMMITTED 的優缺點
READ COMMITTED 算是個黃金中間值。它可以保護你不會讀到髒資料:你不會看到別的 transaction 還沒結束的變更。也就是說,沒有人會讀到「半成品」資訊,這些資訊隨時可能被 rollback 掉。
這個模式比更嚴格的等級(REPEATABLE READ 或 SERIALIZABLE)快,因為不用搞一堆鎖跟額外檢查。它夠輕量又夠穩,所以才會被設成預設值,超適合大部分日常操作。
雖然它能防止 dirty read,READ COMMITTED 還是擋不住:
- 「non-repeatable read」(
Non-Repeatable Read):資料值有可能被別的 transaction 在你查詢之間改掉。 - 「phantom read」(
Phantom Read):別的 transaction 可能加了新資料,影響你的查詢結果。
用 READ COMMITTED 時的小建議
記得結束 transaction: 不要忘了用 COMMIT 或 ROLLBACK,不然可能會有鎖住的問題。
確認隔離等級夠不夠: 如果你一定要保證資料在 transaction 執行期間不會變,考慮用 REPEATABLE READ。
用 index: 這樣 PostgreSQL 查資料跟改資料都會快很多。
範例:訂單處理
假設我們有一張 orders 表,裡面放著訂單資料:
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending'
);
INSERT INTO orders (customer_name, status)
VALUES ('Alice', 'pending'), ('Bob', 'pending');
我們想要把所有狀態是 "pending" 的訂單更新狀態:
BEGIN;
SELECT * FROM orders WHERE status = 'pending';
UPDATE orders SET status = 'completed' WHERE status = 'pending';
COMMIT;
如果在這個過程中,別的 transaction 新增了一筆狀態是 "pending" 的訂單並且 COMMIT,我們這個 transaction 不會處理到那一筆,因為它是在我們查詢之後才加進來的。
這就是「phantom read」的例子。如果你想避免這種情況,就要用 SERIALIZABLE。
READ COMMITTED 這個隔離等級是大多數資料庫(包括 PostgreSQL)的預設選擇。它能防止 dirty read,所以很適合大部分標準操作。但如果你的情境需要很嚴格的一致性,可能就要考慮更嚴格的隔離等級。怎麼選隔離等級,還是要看你的需求跟效能考量啦。
GO TO FULL VERSION