交易與復原:用 ACID 判斷並行風險
本頁為依教材與考古題整理的原創摘要;考古題答案經技術覆核,但不是官方答案。
第一次接觸也沒關係
這堂先懂這些詞
先記住白話意思,不必急著背英文。看到正文時,再把正式名稱接回來。
ACID 交易特性
也會看到:ACID、atomicity、consistency、isolation、durability交易要做到全有全無、維持規則、彼此適度隔離,完成後結果可保存。
- 生活例子:
- 銀行轉帳不能只扣款不入帳,且完成後不能因當機消失。
- 別搞混:
- ACID 的 consistency 是符合資料規則,不等同分散式系統 CAP 的 consistency。
資料庫鎖定與兩階段鎖定
也會看到:shared lock、exclusive lock、two-phase locking、2PL讀取可使用 shared lock,修改需使用 exclusive lock;2PL 以先取得、後釋放的兩階段約束交易排程。
- 生活例子:
- 像多人可同時看同一本公告,但有人要改內容時必須先取得獨占編輯權。
- 別搞混:
- 2PL 有助於 serializability,但仍可能發生 deadlock,也不是資料復原機制。
死結
也會看到:deadlock、死鎖多個工作各自占著資源又互等對方釋放,因而永遠無法前進。
- 生活例子:
- 兩台車在窄巷互不退讓,彼此都卡住。
- 別搞混:
- 等待很久不一定是死結;死結有形成環狀等待等特定條件。
隔離等級
也會看到:isolation level、dirty read、non-repeatable read、serializable資料庫用不同強度限制並行交易彼此看見未完成變更的程度。
- 生活例子:
- 兩位售票員同時賣最後一張票,需要規則避免重複售出。
- 別搞混:
- 隔離越強通常成本越高;不是所有系統都一律用最高等級。
交易並行異常
也會看到:lost update、dirty read、non-repeatable read多筆交易交錯執行時,可能覆蓋更新、讀到未提交資料,或同一筆資料前後讀到不同值。
- 生活例子:
- 兩位店員同時修改最後一件庫存,後存檔的人可能蓋掉前一人的結果。
- 別搞混:
- 異常來自操作交錯與隔離不足,不是所有同時執行都一定出錯。
預寫式日誌與檢查點
也會看到:write-ahead logging、WAL、UNDO、REDO、checkpoint資料頁寫回前先記錄日誌,故障後再依日誌撤銷或重做;檢查點縮短復原範圍。
- 生活例子:
- 像修改帳本前先寫操作紀錄,停電後可按紀錄還原或補做。
- 別搞混:
- Checkpoint 不是備份,也不代表它之前的所有日誌都能無條件刪除。
從交易邊界與 ACID 出發,判讀 lost update、dirty read、不可重複讀等並行異常,再以 lock、serializability、log 與 checkpoint 建立完整的復原觀念。
先抓住這幾件事
- 用具體操作解釋 Atomicity、Consistency、Isolation、Durability
- 從 interleaved schedule 辨識 lost update、dirty read 與 non-repeatable read
- 說明 shared/exclusive lock、two-phase locking 與 deadlock 的關係
- 依 write-ahead logging、UNDO/REDO 與 checkpoint 判斷故障後處理
先想像這個場景
轉帳到一半,銀行突然斷電
你從帳戶 A 轉 200 元給朋友:A 扣款與 B 入帳必須被當成同一件事。如果扣完款就斷電,系統不能留下錢憑空消失的結果;如果兩位行員同時用舊餘額更新同一帳戶,也不能讓其中一筆修改悄悄被蓋掉。
先別急著往下看,花十秒想一想:
畫面已經顯示「轉帳成功」,但最新資料頁還沒寫回磁碟就斷電,重新開機後應該取消這筆轉帳,還是把它恢復完成?
把故事換成電腦語言
| 生活中的角色 | 對應到 | 技術概念 |
|---|---|---|
| 扣款與入帳合在同一筆轉帳 | 一個 transaction 的操作邊界 | |
| 銀行正式確認轉帳成功 | COMMIT;結果進入必須保留的狀態 | |
| 尚未確認就把途中修改撤回 | ROLLBACK 或復原時 UNDO 未提交交易 | |
| 先留下可追查的交易紀錄 | write-ahead logging:log 必須先於 data page 持久化 | |
| 兩位行員同時更新同一筆餘額 | 並行 transactions 可能產生 lost update 等異常 |
題目出現這些字,先想到
- all or nothing、只完成一半:先想到 Atomicity。
- 已 COMMIT、故障後仍要存在:先想到 Durability 與 REDO。
- 讀到之後可能 ROLLBACK 的值:先想到 dirty read。
- 兩次都用舊值計算,後寫者覆蓋前寫者:先想到 lost update。
1.交易是不可拆散的工作單位
Transaction(交易/事務)是資料庫中一組不可分割的操作序列 — 要嘛全部成功(commit),要嘛全部取消(rollback/abort),不能只做一半。這個「全有或全無」的特性叫做 atomicity。 【經典範例:銀行轉帳】A 帳戶轉 1000 元到 B 帳戶,包含兩個操作:(1) A 帳戶餘額 - 1000。(2) B 帳戶餘額 + 1000。如果系統在 (1) 完成後、(2) 完成前當機,A 的錢扣了但 B 沒收到 — 1000 元憑空消失。Transaction 機制確保:如果 (2) 失敗,(1) 也會被 rollback,A 的餘額恢復原狀。 【Transaction 的生命週期】 (1) BEGIN:開始交易。 (2) 執行一連串的讀取和寫入操作。 (3) COMMIT:所有操作都成功,永久寫入資料庫。 (4) 或 ROLLBACK/ABORT:放棄所有操作,回到交易開始前的狀態。 交易一旦 commit,其結果必須是永久的(durability),即使系統隨後當機也不能遺失。這通常透過 write-ahead log(WAL)實現 — 在修改資料頁之前,先把修改記錄寫到日誌檔案。當機復原時,從日誌重播或回滾未完成的交易。 【考試連結】考古題最常考的情境是判斷「如果系統在某個時間點當機,資料庫的狀態是什麼」。答案取決於交易是否已經 commit:已 commit → 結果保留(redo if needed);未 commit → 結果取消(undo)。
- COMMIT 表示交易結果成為正式狀態
- ROLLBACK 放棄尚未提交的修改
- 交易邊界應對應一個完整的業務不變量
- 資料庫 constraint 與應用程式檢查共同守護 consistency
2.ACID 四個字母各管一件事
ACID 是交易必須滿足的四個特性,每個字母解決一個不同的問題: 【A - Atomicity(原子性)】交易中的所有操作要嘛全部成功,要嘛全部取消。解決的問題:避免部分完成的操作留下不一致的資料狀態。實現機制:undo log — 記錄每個操作的反向操作,rollback 時依序執行。 【C - Consistency(一致性)】交易執行前後,資料庫必須保持在一個合法的狀態。所有的完整性約束(如 primary key 唯一、foreign key 參照存在、CHECK 約束成立)在交易結束後仍然滿足。例如轉帳後,所有帳戶餘額的總和不變。注意:ACID 的 Consistency 是「資料完整性約束」的概念,和 CAP theorem 的 Consistency(所有節點看到相同資料)是不同的。 【I - Isolation(隔離性)】多個交易並行執行時,每個交易感覺自己是唯一在執行的,看不到其他交易的中間狀態。理想情況下,並行執行的結果和某個序列執行(一個接一個)的結果相同 — 這叫做 serializability。實務上為了效能,會放寬 isolation level。 【D - Durability(持久性)】交易一旦 commit,其修改永久保存,即使系統隨後斷電或當機。實現機制:redo log + 定期 checkpoint — 當機復原時,從 checkpoint 開始重播 redo log 中已 commit 的交易。 【考試連結】選擇題常給一個情境問「違反了 ACID 的哪個特性」。例如「轉帳只扣了 A 但沒加到 B」→ 違反 Atomicity;「兩個交易同時修改同一筆資料導致結果錯誤」→ 違反 Isolation。最大陷阱是混淆 ACID-C 和 CAP-C。
- Atomicity 常靠 log 與 UNDO 支援
- Isolation 的強弱由 isolation level 與 concurrency control 決定
- Durability 不代表每次更新都立刻把資料頁同步到磁碟
- Consistency 不是 DBMS 單方面理解所有商業規則
3.並行執行為何會出錯
在多用戶環境中,多個交易同時存取和修改相同的資料可能導致三種經典的並行問題: 【Dirty Read(髒讀)】交易 T1 修改了資料但尚未 commit,交易 T2 讀取了 T1 修改後的值。如果 T1 隨後 rollback,T2 讀到的資料就是無效的(從未實際存在的值)。例如:T1 把價格從 100 改成 150(還沒 commit),T2 讀到 150 並據此計算。T1 rollback 後價格回到 100,但 T2 已經用了錯誤的 150。 【Non-repeatable Read(不可重複讀)】交易 T1 讀取某筆資料後,交易 T2 修改並 commit 了該筆資料,T1 再次讀取時得到不同的值。同一個交易中,兩次讀取同一筆資料的結果不一致。例如:T1 讀到帳戶餘額 5000,T2 把餘額改成 3000 並 commit,T1 再讀時看到 3000。 【Phantom Read(幻讀)】交易 T1 用某個條件查詢得到一組結果,交易 T2 隨後插入或刪除了符合該條件的資料並 commit,T1 再次用相同條件查詢時得到不同數量的 row。例如:T1 查詢「GPA > 3.5 的學生」得到 10 人,T2 新增一位 GPA 3.8 的學生並 commit,T1 再查時得到 11 人 — 多出的一人像「幽靈」般出現。 【Isolation Levels】SQL 標準定義四個隔離等級,從低到高: (1) READ UNCOMMITTED:允許 dirty read(最低隔離) (2) READ COMMITTED:防止 dirty read,但允許 non-repeatable read (3) REPEATABLE READ:防止 dirty read 和 non-repeatable read,但允許 phantom read (4) SERIALIZABLE:防止所有三種問題(最高隔離,但效能最差) 【考試連結】題目常給一個交易執行順序,問會發生哪種問題,或問哪個 isolation level 能防止特定問題。記住三者的嚴重程度:dirty read > non-repeatable read > phantom read,對應的 isolation level 由低到高排列。
- serial schedule 完成一個交易後才執行下一個
- serializable schedule 可以交錯,但結果必須等價於某個 serial order
- dirty read 的來源是讀到之後可能 rollback 的值
- lost update 常出現在 read-modify-write 缺乏保護時
4.鎖與日誌分別處理並行和故障
資料庫用兩套機制分別處理並行控制和故障復原:Lock(鎖)處理並行,Log(日誌)處理故障。 【Lock-based concurrency control】 • Shared lock(S lock / 讀鎖):多個交易可以同時持有同一筆資料的 S lock。持有 S lock 只能讀取,不能修改。 • Exclusive lock(X lock / 寫鎖):一次只有一個交易能持有。持有 X lock 可以讀取和修改。其他交易既不能讀也不能寫(除非使用 MVCC 等技術)。 相容性:S lock 和 S lock 相容(多個讀者可並行),S lock 和 X lock 不相容,X lock 和 X lock 不相容。 【Two-Phase Locking(2PL)】確保 serializability 的協定:(1) Growing phase — 交易只能取得鎖,不能釋放。(2) Shrinking phase — 交易只能釋放鎖,不能取得。一旦釋放任何一個鎖,就進入 shrinking phase,不能再取得新鎖。2PL 保證了可序列化性,但可能導致 deadlock。 【Log-based recovery】 • Write-Ahead Logging(WAL):在修改資料頁之前,必須先把修改記錄(log record)寫到持久的日誌檔案。Log record 包含:交易 ID、修改的資料項、修改前的值(before image,用於 undo)、修改後的值(after image,用於 redo)。 • 復原流程:當機重啟後,掃描日誌:(1) Redo 所有已 commit 的交易(確保 durability)。(2) Undo 所有未 commit 的交易(確保 atomicity)。Checkpoint 機制定期記錄「到此時為止所有已 commit 的交易」,縮小復原時需要掃描的日誌範圍。 【考試連結】題目常問「系統在某時間點當機,以下哪些交易需要 redo、哪些需要 undo」。規則簡單:checkpoint 之後 commit 的 → redo;checkpoint 之後開始但未 commit 的 → undo。
- deadlock 是形成等待環,starvation 是某交易長期得不到資源
- UNDO 撤回未提交交易,REDO 重播已提交但尚未落盤的修改
- WAL 的核心順序是 log before data page
- checkpoint 是復原起點,不是新的 ACID 屬性
一起拆題目
範例 1:帳戶 A=1000、B=500。交易 T 要從 A 轉 200 到 B;系統在 A 扣款後、B 入帳前故障。ACID 如何處理?
- 把扣 A 與加 B 放在同一 transaction,兩步共同維持總額不變的 consistency rule。
- 若 T 尚未 COMMIT,復原程序利用 log UNDO A 的扣款,滿足 atomicity。
- 若 COMMIT record 已持久化但資料頁尚未全寫回,復原程序 REDO 已提交修改,滿足 durability。
- 其他交易不應在 T 提交前看到只扣款未入帳的中間狀態,這是 isolation 的工作。
所以答案是:未提交就完整 UNDO;已提交但資料頁未落盤就 REDO。任何情況都不應留下只扣 A、未加 B 的正式狀態。
範例 2:X 初值 100。T1 與 T2 都先讀 X;T1 寫入 110,T2 依自己先前讀到的 100 寫入 80。發生什麼問題?如何避免?
- T1、T2 都以舊值 100 計算,T2 最後寫入會覆蓋 T1 的 +10。
- 最後 X=80,而不是依兩筆更新某一序列得到的 90,這是 lost update。
- 可在 read-modify-write 前取得 exclusive lock,讓另一交易等待。
- 也可使用具衝突檢測的 optimistic concurrency control,在提交時發現版本已改變並重試。
所以答案是:這是 lost update;用適當的 exclusive lock、serializable isolation 或版本檢查重試,可確保結果等價於 T1/T2 的某個循序順序。
這裡最容易選錯
- 把 ACID 的 A 誤記為 Availability,或把 I 誤記為 Integrity
- 認為 isolation 就是交易完全不能並行;正確目標是並行結果具備指定的隔離保證
- 以為 two-phase locking 不會 deadlock;它可保證 conflict serializability,但仍可能形成等待環
- 把 checkpoint 當成完整備份,或認為 checkpoint 前的所有 log 都可立即刪除
換你快速判斷
先在心中作答,再展開答案。答不出來時,回頭找本課的對照關係。
1ACID 四個字母分別代表什麼?
Atomicity、Consistency、Isolation、Durability。
常見陷阱是把 A 誤寫成 Availability、把 I 誤寫成 Integrity;這兩個都不屬於 ACID 的正式展開。
2Atomicity 與 Durability 在故障復原時如何區分?
Atomicity 要撤回未提交交易的部分結果;Durability 要保住或重做已提交交易的結果。
典型復原中,未提交交易需要 UNDO,已提交但資料頁尚未落盤的交易需要 REDO。
3serial schedule 與 serializable schedule 有何差異?
Serial schedule 完全不交錯;serializable schedule 可以交錯,但結果必須等價於某個 serial order。
隔離控制不一定禁止並行,而是讓並行結果保有可接受的循序語意。
4lost update 是怎麼發生的?
兩個交易讀到同一舊值,各自計算後寫回;後寫者覆蓋先寫者的修改。
可用 exclusive lock、serializable isolation,或 optimistic version check 阻止未察覺的覆蓋。
5two-phase locking 為何仍可能 deadlock?
2PL 限制取得與釋放鎖的階段,可保證 conflict serializability;但不同交易仍可能各持有一把鎖並互等對方的鎖。
Serializability 與 deadlock freedom 是不同性質。Deadlock 需靠等待圖偵測、timeout、預防或 victim rollback 處理。
6Write-Ahead Logging 的最重要順序規則是什麼?
修改過的資料頁寫入持久儲存前,描述該修改的 log record 必須先持久化。
如此故障後才有足夠資訊做 UNDO 或 REDO。Checkpoint 只縮短復原範圍,不等於完整備份。
最後用考古題驗證
本課連結的題目都已通過可重現的技術覆核,可逐題練習與判分。
開始本課考古題練習參考來源
- Computer Science: An Overview, 13th Edition — J. Glenn Brookshear
- 資料庫課程 — 陳士杰(杰哥數位教室)