交易與 ACID 完全指南

理解資料庫交易的四大特性 ACID、交易控制語法,以及隔離級別與底層實現機制


目錄


什麼是交易?

交易(Transaction) 是一組被視為單一邏輯單位的資料庫操作:要嘛全部成功生效,要嘛全部不生效,不會停在中間狀態。

經典例子是轉帳:

A 帳戶扣 100 元
B 帳戶加 100 元

這兩步必須綁在一起。若扣款後系統崩潰、加款沒做,錢就憑空消失。交易確保這兩步「同生共死」。

核心特點

  • 邏輯單元:多個操作打包成一個整體
  • 全有或全無:不允許部分完成
  • 隔離執行:並行的交易彼此不互相干擾
  • 持久結果:一旦提交,結果不會因故障丟失

這四點正好對應 ACID 四大特性。


ACID 四大特性

A — Atomicity(原子性)

交易內的所有操作是不可分割的整體:全部成功提交(COMMIT),或全部撤銷(ROLLBACK),不會只做一半。

扣款成功、加款失敗 → 整筆交易回滾,扣款也撤銷

C — Consistency(一致性)

交易把資料庫從一個合法狀態帶到另一個合法狀態,過程中所有約束(主鍵、外鍵、唯一鍵、CHECK)都必須滿足。

轉帳前後,A + B 的總額不變
任何違反約束的中間狀態都不會被提交

一致性是「目標」,由原子性、隔離性與資料庫約束共同保證。

I — Isolation(隔離性)

多個交易並行執行時,彼此感覺不到對方的中間狀態,結果如同依序執行。隔離程度由隔離級別控制(見後文)。

D — Durability(持久性)

交易一旦提交,結果就永久保存,即使隨後立刻斷電、系統崩潰也不會丟失。通常靠預寫日誌(WAL) 達成。

四特性關係

Atomicity  → 保證「全有或全無」(靠 undo / 回滾)
Durability → 保證「提交不丟」(靠 redo / WAL)
Isolation  → 保證「並行不互擾」(靠鎖 / MVCC)
Consistency→ 上述三者 + 約束 共同達成的最終目標

交易控制語法

BEGIN;                      -- 開始交易(或 START TRANSACTION)

UPDATE accounts SET balance = balance - 100 WHERE id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE id = 'B';

COMMIT;                     -- 提交,使變更永久生效
-- 或
ROLLBACK;                   -- 回滾,撤銷本交易所有變更

自動提交(Autocommit)

多數資料庫預設 autocommit 開啟:每一條 SQL 自成一個交易,執行完立即提交。

-- autocommit 下,這一句就是一個完整交易
UPDATE accounts SET balance = balance - 100 WHERE id = 'A';

-- 要把多句綁成一個交易,需顯式 BEGIN ... COMMIT

應用程式框架(如 ORM)通常會自行管理交易邊界,需留意它何時開始 / 提交。


SAVEPOINT 部分回滾

SAVEPOINT 在交易內設定還原點,可只回滾到某一點,而非整筆交易。

BEGIN;
INSERT INTO orders (id) VALUES (1);

SAVEPOINT sp1;
INSERT INTO orders (id) VALUES (2);   -- 假設這步出錯

ROLLBACK TO sp1;                       -- 只撤銷 id=2,id=1 仍保留
INSERT INTO orders (id) VALUES (3);

COMMIT;                                -- 最終提交 id=1 和 id=3

適合「大交易中容許部分步驟失敗重試」的場景。


隔離級別與並行異常

隔離性不是非黑即白,而是分等級——級別越高越安全,但並行度越低。

三種並行異常

異常 說明
髒讀(Dirty Read) 讀到別的交易尚未提交的資料
不可重複讀(Non-repeatable Read) 同交易內兩次讀同一列,值不同(被別人 UPDATE)
幻讀(Phantom Read) 同交易內兩次相同查詢,列數不同(被別人 INSERT / DELETE)

四個隔離級別

隔離級別 髒讀 不可重複讀 幻讀
Read Uncommitted 可能 可能 可能
Read Committed 不會 可能 可能
Repeatable Read 不會 不會 可能(標準)
Serializable 不會 不會 不會
-- 設定交易隔離級別
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

預設級別差異

  • PostgreSQL、Oracle、SQL Server:預設 Read Committed
  • MySQL(InnoDB):預設 Repeatable Read

隔離級別主要靠 MVCC 與鎖實現,詳細機制見 MVCC 多版本並行控制完全指南。InnoDB 在 Repeatable Read 下還用 Next-Key Lock 防止幻讀,比 SQL 標準更嚴格。


ACID 的底層實現

各特性不是憑空達成,而是靠具體機制:

原子性 → Undo Log

記錄「修改前的舊值」。交易回滾時,用 undo log 把資料還原。

持久性 → Redo Log / WAL

預寫日誌(Write-Ahead Logging, WAL) 的核心規則:先寫日誌,再改資料

提交時 → 變更先寫進 WAL 並落盤
       → 即使資料頁還在記憶體未寫入磁碟,崩潰後也能用 WAL 重做(redo)
  • PostgreSQL:WAL
  • MySQL InnoDB:redo log(與 undo log 並用)
  • 這也是 SQLite 的 WAL 模式所做的事

隔離性 → 鎖 + MVCC

  • :寫-寫衝突時序列化
  • MVCC:讀-寫不互斥(讀舊版本、寫新版本)

一致性 → 約束 + 上述機制

主鍵、外鍵、唯一鍵、CHECK 約束在交易提交時必須全部滿足,配合原子性與隔離性,保證資料始終合法。


實戰範例

範例 1:轉帳(原子性)

BEGIN;

UPDATE accounts SET balance = balance - 100 WHERE id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE id = 'B';

-- 任一步失敗就 ROLLBACK,兩步同生共死
COMMIT;

範例 2:檢查後再操作(隔離性 + 鎖)

避免兩個交易同時扣到餘額變負,用 FOR UPDATE 鎖住該列(當前讀):

BEGIN;

SELECT balance FROM accounts WHERE id = 'A' FOR UPDATE;  -- 鎖住,別人要改得等
-- 應用層判斷 balance >= 100
UPDATE accounts SET balance = balance - 100 WHERE id = 'A';

COMMIT;

範例 3:應用程式中的交易(虛擬碼)

try:
    begin()
    扣款(A, 100)
    加款(B, 100)
    commit()
except:
    rollback()        # 出錯一律回滾

分散式交易

當資料跨多個資料庫 / 服務時,單機交易不夠用,需要分散式交易機制。

兩階段提交(2PC, Two-Phase Commit)

階段 1(Prepare):協調者問所有參與者「能提交嗎?」,各自準備並回覆
階段 2(Commit): 全部說 OK → 通知全部提交;任一說 NO → 通知全部回滾
  • 優點:保證強一致
  • 缺點:協調者單點故障、過程中資源被鎖住、效能差

Saga 模式

把長交易拆成一串本地交易,每步都有對應的補償操作;某步失敗時,反向執行已完成步驟的補償。

訂位 → 付款 → 出票
若出票失敗 → 退款(補償付款)→ 取消訂位(補償訂位)
  • 優點:不長時間鎖資源,適合微服務
  • 缺點:只保證最終一致,不是強一致,補償邏輯複雜

最佳實踐

1. 交易盡量短

交易越長,持有鎖越久、阻擋 MVCC 舊版本回收越久(造成膨脹)。避免在交易中間等待使用者輸入或呼叫慢速外部服務。

2. 不要在交易中做非資料庫的副作用

❌ begin → 寫 DB → 寄 email → commit
   (若 commit 失敗,email 已寄出無法收回)
✅ commit 成功後,再觸發寄 email

3. 選對隔離級別

預設(Read Committed / Repeatable Read)能滿足多數情況。需要嚴格一致(如對帳、庫存)才升到 Serializable,並準備好處理序列化失敗的重試。

4. 一定要處理 ROLLBACK

任何錯誤路徑都要能回滾,別讓交易懸著不提交也不回滾(會一直持有鎖)。


常見問題

問題 1:ACID 分別由什麼保證?

原子性靠 undo log(回滾)、持久性靠 redo log / WAL、隔離性靠鎖 + MVCC、一致性是前三者加約束共同達成的結果。

問題 2:COMMIT 之後還會丟資料嗎?

正常情況不會——這就是持久性。提交時變更已寫入 WAL 並落盤,崩潰後能重做。除非磁碟損壞或關閉了同步落盤設定(如 MySQL 的 innodb_flush_log_at_trx_commit 設成非 1)。

問題 3:隔離級別越高越好嗎?

不是。級別越高越安全,但並行度越低、越容易卡鎖或產生序列化失敗。要在一致性需求與效能間取捨,多數場景用預設即可。

問題 4:autocommit 是什麼?要關掉嗎?

autocommit 讓每句 SQL 自成交易、立即提交。不用刻意關閉;當你需要把多句綁成一個交易時,顯式用 BEGIN ... COMMIT 即可。

問題 5:交易能解決所有並行問題嗎?

不能。跨多服務 / 多資料庫的操作需要分散式交易(2PC / Saga)。單機交易也無法防止應用層的邏輯競態(如先查再寫),需配合 FOR UPDATE 鎖或 UPSERT


總結

核心要點

  • 交易把一組操作打包成全有或全無的邏輯單元
  • ACID:原子性(全有全無)、一致性(始終合法)、隔離性(並行不互擾)、持久性(提交不丟)
  • 控制語法:BEGIN / COMMIT / ROLLBACKSAVEPOINT 可部分回滾
  • 隔離級別決定能容忍哪些並行異常,主要靠 MVCC + 鎖實現
  • 底層:undo log(原子性)、redo/WAL(持久性)、鎖 + MVCC(隔離性)
  • 跨服務需分散式交易(2PC 強一致 / Saga 最終一致)

快速參考

特性 保證什麼 實現機制
Atomicity 全有或全無 undo log / 回滾
Consistency 狀態始終合法 約束 + 其他三者
Isolation 並行不互擾 鎖 + MVCC
Durability 提交不丟失 redo log / WAL
語法 作用
BEGIN 開始交易
COMMIT 提交、永久生效
ROLLBACK 撤銷全部變更
SAVEPOINT / ROLLBACK TO 設還原點 / 部分回滾

建立日期:2026-06-17

🔗相關文章