目錄
什麼是交易?
交易(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/ROLLBACK,SAVEPOINT可部分回滾 - 隔離級別決定能容忍哪些並行異常,主要靠 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