有時候 transaction 就像超級英雄電影裡的角色一樣,能在故障、錯誤或系統出包時拯救我們的資料庫。如果你在處理一個需要多個操作、而且不能拆開來做的任務,transaction 就能確保這些操作會一起完成。我們來看看處理付款時,transaction 是怎麼發揮作用的。
處理付款
想像一下經典情境:你有兩個銀行帳戶,想把錢從一個帳戶轉到另一個。這可不是「按一下按鈕」那麼簡單。我們得確保從一個帳戶扣款、另一個帳戶加款都正確。任何一個步驟出錯都可能很慘:要嘛兩個帳戶都沒變,要嘛餘額亂掉(像是錢突然消失或「憑空出現」)。
情境:帳戶間轉帳
來看一下我們的程式碼。要像讀來自遙遠銀河的訊息一樣仔細看:
-- 開始 transaction
BEGIN;
-- 步驟 1. 從發送者帳戶扣款
UPDATE accounts
SET balance = balance - 100
WHERE account_id = 1;
-- 步驟 2. 把錢加到收款者帳戶
UPDATE accounts
SET balance = balance + 100
WHERE account_id = 2;
-- 一切順利?那就存檔!
COMMIT;
這裡重點是什麼?
- 如果在
步驟 1或步驟 2有什麼出錯(像是 SQL 寫錯、錢不夠),transaction 可以用 ROLLBACK 來「倒帶」,資料就會回到原本的狀態。 COMMIT保證只有所有步驟都成功才會真的寫進資料庫。
加上餘額檢查
那如果發送者的錢不夠轉呢?我們來加個餘額檢查,避免讓他「負債」。
-- 開始 transaction
BEGIN;
-- 取得發送者目前餘額
DO $$
DECLARE
current_balance NUMERIC;
BEGIN
SELECT balance INTO current_balance FROM accounts WHERE account_id = 1;
-- 檢查錢夠不夠
IF current_balance >= 100 THEN
-- 錢夠就轉帳
UPDATE accounts
SET balance = balance - 100
WHERE account_id = 1;
UPDATE accounts
SET balance = balance + 100
WHERE account_id = 2;
-- 存檔
COMMIT;
ELSE
-- 錢不夠就倒帶
ROLLBACK;
RAISE NOTICE '轉帳金額不足!';
END IF;
END $$;
這裡有什麼有趣的?
- 我們用 PL/pgSQL 的區塊,透過
IF來檢查條件。如果餘額小於要轉的金額,transaction 會被拒絕,什麼都不會變。 ROLLBACK會取消已經做的變更(雖然這裡還沒動到資料,但這樣寫比較保險)。
這堂課是講 transaction 的真實應用場景,所以我特地舉了一個生活中的例子。這裡有 stored procedure,用 PL-SQL 寫的。我覺得你們已經有足夠經驗,應該看得懂這怎麼運作。以後我們還會回來講 PL-SQL,甚至會看更複雜的例子。
在 transaction 裡批次更新資料
transaction 不只用在轉帳這種情境。假設我們有個電商資料庫,每天有很多訂單狀態會變,比如從「運送中」變成「已完成」。要怎麼一次更新很多筆資料,而且如果出錯還能全部倒回去?當然就是用 transaction 啦!
再來看一個情境:更新訂單狀態。
範例如下:
-- 開始 transaction
BEGIN;
-- 步驟 1. 更新已經過期的訂單
UPDATE orders
SET status = 'completed'
WHERE delivery_date < CURRENT_DATE;
-- 步驟 2. 通知更新成功
RAISE NOTICE '所有訂單狀態都更新成功啦。';
-- 實際寫入
COMMIT;
如果出錯怎麼辦?
總是有可能出錯。比如你不小心忘了寫 WHERE 條件,結果所有訂單都變成 completed。為了避免這種事,記得要結束 transaction 或明確倒帶。
來看倒帶的情境:
-- 開始 transaction
BEGIN;
-- 步驟 1. 嘗試更新訂單但沒寫條件(糟糕,出錯了!)
UPDATE orders
SET status = 'completed';
-- 因為錯誤倒帶
ROLLBACK;
-- 現在訂單都沒被改到
用 SAVEPOINT 增加一點「彈性」
有時候你不想整個 transaction 都倒帶。如果你的流程有好幾個步驟,可能只想倒回其中一個。這時 SAVEPOINT 就派上用場啦!
現在我們的情境是:多步驟處理,但只想倒帶其中一個步驟。
想像你在處理一個訂單,有幾個步驟:從倉庫扣貨、更新訂單狀態、通知客戶。如果通知沒發成功,你只想倒帶這個步驟,但其他資料要保留。
-- 開始 transaction
BEGIN;
-- 步驟 1. 從倉庫扣貨
UPDATE products
SET stock = stock - 1
WHERE product_id = 101;
-- 設定還原點
SAVEPOINT step1;
-- 步驟 2. 更新訂單狀態
UPDATE orders
SET status = 'shipped'
WHERE order_id = 202;
-- 嘗試通知客戶
SAVEPOINT step2;
-- 哎呀,通知出錯!
ROLLBACK TO SAVEPOINT step2;
-- 決定還是安全結束 transaction
COMMIT;
結論
transaction 不只是技術工具,更是你資料完整性的保證。它們能防止「骨牌效應」,避免一個錯誤毀掉整個系統。每次你要做多個相關操作時,問問自己:「如果其中一個失敗會怎樣?」如果答案是「災難」,那就該用 transaction 了。 記住:花幾分鐘寫 transaction,總比花幾小時救資料來得划算。你的使用者(還有你的心情)都會感謝你!
GO TO FULL VERSION