PostgreSQL 裡的交易(transaction)會保證隔離性——這是 ACID 的重點之一。為了做到這點,系統會用到鎖(lock)。鎖就是一種機制,確保多個交易不會「搶」同一筆資料或同一張表。想像你在超市排隊結帳:一次只能一個人結帳。鎖的運作方式就有點像這樣,只是它是在管控資料表裡的資料存取。
鎖的主要類型
在 PostgreSQL 裡有幾種不同的鎖,有些你可能已經在 EXPLAIN ANALYZE 裡看過:
ROW EXCLUSIVE(列專屬鎖) — 當你改動某一列的資料時會出現。這是最常見的鎖類型,比如你在做INSERT、UPDATE或DELETE的時候。SHARE(共用鎖) — 用在那些需要保證資料不會被改動的操作,比如SELECT ... FOR SHARE。EXCLUSIVE(專屬鎖) — 會阻擋其他所有操作,除了讀取。
可以說,鎖就像是看門狗一樣,守護著我們的資料。
鎖的問題:會出什麼狀況?
現在我們知道什麼是鎖了,來看看它們可能帶來什麼麻煩。最大條的問題就是死結(deadlock)。死結就是兩個(或更多)交易互相等對方釋放鎖,結果大家都卡住,進入無限等待的迴圈。通常會像這樣:
- 交易 1 鎖住了列 A,想要存取列 B。
- 交易 2 鎖住了列 B,想要存取列 A。
- 因為兩列都被鎖住,沒有一個交易能完成。
死結範例
想像一下工程師遇到 deadlock 的痛苦場景:
-- 交易 1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 同時
-- 交易 2
BEGIN;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
-- 交易 1 嘗試鎖住 id = 2
UPDATE accounts SET balance = balance - 100 WHERE id = 2;
-- 交易 2 嘗試鎖住 id = 1
UPDATE accounts SET balance = balance + 200 WHERE id = 1;
登愣!每個交易都被對方鎖住了。PostgreSQL 遲早會發現這種情況,然後丟出死結錯誤。
怎麼避免死結?
- 資料存取順序要一致。 盡量每次都用一樣的順序存取資料。如果交易 1 先鎖列 A 再鎖列 B,那交易 2 也要照這個邏輯:先列 A,再列 B。
-- 正確的存取順序
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
COMMIT;
讓交易盡量短。 交易拖太久,死結的機率就高。記得快點做完:
BEGIN,做完所有要改的東西,馬上COMMIT。用適合的交易隔離等級。 如果你的需求沒那麼嚴格,可以考慮用
READ COMMITTED隔離等級。這樣可以避掉一些鎖的問題。
鎖的 timeout 跟診斷
PostgreSQL 有一些工具可以幫你診斷和預防鎖的問題。比如你可以設定鎖的最大等待時間:
SET lock_timeout = '5s'; -- 設定鎖的 timeout 為 5 秒
如果鎖被持有超過這個時間,交易就會被中斷,這樣就不會整個卡死。
在 PostgreSQL 追蹤鎖
有個很實用的指令可以監控鎖,就是 pg_locks。它會顯示系統裡所有的鎖:
SELECT * FROM pg_locks;
你可以看到哪些交易持有鎖、哪些在等鎖。debug 死結時超級有用。
LOCK 的細節與手動鎖定物件
如果你想手動管理鎖,可以用 LOCK 指令:
LOCK TABLE orders IN ACCESS EXCLUSIVE MODE;
注意:ACCESS EXCLUSIVE 是最強的鎖,會擋掉所有其他操作,連 SELECT 都不行。只有在特殊情況(比如改表結構)才用它。
如果你只想鎖某幾列,可以用 SELECT ... FOR UPDATE:
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
這樣可以保證其他交易在你這個交易結束前不能改這些列。
關於鎖你要記得什麼?
鎖不是壞東西,是工具。它們幫你守住資料完整性,但要小心用。幾個重點建議:
- 沒必要就不要開交易。
- 交易要快點結束。
- 存取資料順序要一致。
- 用適合你需求的隔離等級。
現在你已經準備好跟鎖和死結打交道了!祝你 pg_locks 在手,ACID 智慧常伴!
GO TO FULL VERSION