☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
sql 中級

Transaction 交易控制

什麼是 Transaction?

比喻:Transaction 就像 ATM 轉帳 🏧

你從 A 帳戶轉 1000 元到 B 帳戶:

  1. A 帳戶 -1000
  2. B 帳戶 +1000

如果步驟 1 成功但步驟 2 失敗,錢就消失了! Transaction 確保「全部成功」或「全部失敗」。


ACID 四大特性

特性 英文 說明
原子性 Atomicity 全部成功或全部失敗,不會只做一半
一致性 Consistency 交易前後,資料保持一致(餘額不會變負)
隔離性 Isolation 多個交易同時進行不會互相干擾
持久性 Durability 一旦 COMMIT,資料永久保存

基本語法

BEGIN;                               -- ← 開始交易

    -- 步驟 1:A 扣款
    UPDATE Accounts
    SET Balance = Balance - 1000
    WHERE Id = 1;

    -- 步驟 2:B 入款
    UPDATE Accounts
    SET Balance = Balance + 1000
    WHERE Id = 2;

COMMIT;                              -- ← 確認,全部生效

逐行解析:

BEGIN;                  -- 開始一個交易
                        -- 從此開始的所有操作都是「暫定的」

UPDATE ... (A 扣款)     -- 暫時從 A 扣 1000
UPDATE ... (B 入款)     -- 暫時給 B 加 1000

COMMIT;                 -- 全部沒問題 → 正式寫入資料庫
                        -- 如果中間任何一步出錯 → 用 ROLLBACK

ROLLBACK — 復原

BEGIN;

    UPDATE Accounts SET Balance = Balance - 1000 WHERE Id = 1;
    UPDATE Accounts SET Balance = Balance + 1000 WHERE Id = 2;

    -- 發現轉錯人了!
    ROLLBACK;                        -- ← 取消全部,回到 BEGIN 之前的狀態

SAVEPOINT — 存檔點

BEGIN;

    INSERT INTO Orders (CustomerId, Amount) VALUES (1, 500);
    SAVEPOINT sp1;                   -- ← 設一個存檔點

    INSERT INTO OrderItems (OrderId, Product) VALUES (1, 'A');
    INSERT INTO OrderItems (OrderId, Product) VALUES (1, 'B');

    -- 發現商品 B 有問題
    ROLLBACK TO sp1;                 -- ← 回到 sp1,只取消 OrderItems
                                     -- Orders 的 INSERT 還在!

    -- 重新插入正確的資料
    INSERT INTO OrderItems (OrderId, Product) VALUES (1, 'C');

COMMIT;                              -- ← 最終:Orders + 商品 A 和 C

隔離等級(Isolation Level)

等級 說明 問題
READ UNCOMMITTED 可讀其他交易未提交的資料 髒讀
READ COMMITTED 只讀已提交的資料(PostgreSQL 預設) 不可重複讀
REPEATABLE READ 同一交易中多次讀取結果相同 幻讀
SERIALIZABLE 完全隔離,像排隊一個一個來 效能最差
-- 設定隔離等級
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

    SELECT Balance FROM Accounts WHERE Id = 1;
    -- 在 SERIALIZABLE 下,其他交易無法修改這筆資料
    UPDATE Accounts SET Balance = Balance - 100 WHERE Id = 1;

COMMIT;

實用範例:安全轉帳

CREATE OR REPLACE FUNCTION transfer_money(
    from_id INT,
    to_id INT,
    amount DECIMAL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
    from_balance DECIMAL;
BEGIN
    -- 檢查餘額
    SELECT Balance INTO from_balance
    FROM Accounts WHERE Id = from_id
    FOR UPDATE;                      -- ← 鎖定這列,防止同時被改

    IF from_balance < amount THEN
        RAISE EXCEPTION '餘額不足!目前餘額:%', from_balance;
    END IF;

    -- 扣款
    UPDATE Accounts SET Balance = Balance - amount WHERE Id = from_id;
    -- 入款
    UPDATE Accounts SET Balance = Balance + amount WHERE Id = to_id;
    -- 記錄
    INSERT INTO TransferLog (FromId, ToId, Amount, TransferAt)
    VALUES (from_id, to_id, amount, CURRENT_TIMESTAMP);
END;
$$;

死鎖(Deadlock)

交易 A:鎖住 Row 1,等待 Row 2
交易 B:鎖住 Row 2,等待 Row 1
→ 互相等待,永遠不會結束 = 死鎖!
-- 預防死鎖:統一鎖定順序
-- ✅ 好:永遠先鎖 Id 小的
BEGIN;
    SELECT * FROM Accounts WHERE Id = 1 FOR UPDATE;
    SELECT * FROM Accounts WHERE Id = 2 FOR UPDATE;
COMMIT;

-- ❌ 壞:A 先鎖 1 再鎖 2,B 先鎖 2 再鎖 1 → 死鎖

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊