精確定義 ACID 四個性質,並知道每個性質由資料庫的哪個機制負責(以及哪個其實是應用程式的責任)。
以衝突可序列化與優先圖判定一個排程是否正確。
辨認 dirty read、non-repeatable read、phantom、lost update、write skew、read skew 等異常,並寫出重現它們的交錯時序。
說明 ANSI 四個隔離等級的定義、Berenson 等人對它的批評,以及 PostgreSQL/MySQL/Oracle 各自的實際行為。
理解 snapshot isolation 為何不是可序列化,以及如何處理 serialization failure。
交易是什麼
是一組被視為單一工作單位的資料庫操作:要嘛全部生效,要嘛全部不生效。 它是資料庫對應用程式最重要的抽象——讓開發者在面對當機與並行時,仍能以「一次只有我在操作、而且操作不會做到一半」的方式思考。
BEGIN; -- 或 START TRANSACTION
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
SAVEPOINT before_fee; -- 部分回滾點
UPDATE accounts SET balance = balance - 5 WHERE id = 1; -- 手續費
ROLLBACK TO SAVEPOINT before_fee; -- 取消手續費,但保留第一筆更新
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- 或 ROLLBACK 放棄全部
沒有明確 時,多數系統處於 autocommit:每個語句自成一筆交易。
ACID
交易中的操作「全有或全無」。由日誌(undo 資訊)或 shadow paging 實現; PostgreSQL 靠 MVCC——中止的交易所寫的版本只是被標為無效,不需要物理回復(第 9 章)。
交易把資料庫從一個合法狀態帶到另一個合法狀態。資料庫只能強制它知道的規則(主鍵、外鍵、CHECK、觸發器); 業務不變量(「帳戶總額守恆」)是應用程式的責任——C 是 ACID 中唯一不完全由資料庫保證的字母。
並行執行的交易彼此看不到對方的中間狀態,結果等價於某種序列執行。由並行控制(鎖、MVCC、OCC)實現,是本篇的主軸。
一旦 commit,即使當機、斷電也不會遺失。由 WAL + fsync(第 8 章)實現; PostgreSQL 的 允許以「最多遺失最後幾百毫秒已回報成功的交易」換取吞吐。
排程與可序列化
排程(schedule)是多筆交易操作的交錯順序。序列排程是交易一個接一個執行; 一個排程若效果等價於某個序列排程,稱為可序列化(serializable)——這是隔離性的黃金標準。
兩個操作衝突的條件:來自不同交易、存取同一資料項、且至少一個是寫入。三種衝突:
R–W(unrepeatable read):\(T_1\) 讀 \(A\) 後 \(T_2\) 寫 \(A\)。
W–R(dirty read):\(T_1\) 寫 \(A\) 後 \(T_2\) 讀 \(A\)。
W–W(lost update):\(T_1\) 寫 \(A\) 後 \(T_2\) 寫 \(A\)。
衝突可序列化:能透過交換不衝突的相鄰操作變成序列排程。判定方法:畫優先圖(precedence graph)—— 節點是交易,若 \(T_i\) 的某操作與 \(T_j\) 的操作衝突且 \(T_i\) 在前,畫 \(T_i \to T_j\)。無環 \(\Leftrightarrow\) 衝突可序列化,拓撲排序就是等價的序列順序。
View serializability 是更寬鬆的定義(允許 blind write 的特例),但判定是 NP-complete,實務系統都以衝突可序列化為目標。
此外還要考慮可復原性:若 \(T_2\) 讀了 \(T_1\) 的寫入,\(T_2\) 必須在 \(T_1\) 之後 commit(recoverable); 否則 \(T_1\) 中止時 \(T_2\) 已 commit 的結果建立在不存在的資料上。更強的 strict 排程(不讀也不覆寫未 commit 的資料) 避免連鎖中止(cascading abort)並讓 undo 變簡單——這是多數系統實際採用的。
異常現象
以下以兩筆交易 \(T_1\)、\(T_2\) 的時間軸呈現(時間由上而下)。
lP5.6cmP5.6cm 異常 & 時序 & 後果
Dirty read & \(T_1\): (未 commit)
\(T_2\): 讀到
\(T_1\): & \(T_2\) 看到從未存在的值。
Non-repeatable read & \(T_1\): 讀 得 5
\(T_2\):
\(T_1\): 再讀 得 7 & 同一交易內兩次讀同一列結果不同。
Phantom & \(T_1\): 得 10
\(T_2\):
\(T_1\): 同一查詢得 11 & 集合層級的不可重複讀:多出「幽靈」列。
Lost update & \(T_1\): 讀
\(T_2\): 讀
\(T_1\):
\(T_2\): & 兩次加一只加了一次。
Write skew & 約束:至少一位醫師值班。目前 Alice、Bob 都值班。
\(T_1\): 查到 2 人值班,Alice 下班
\(T_2\): 查到 2 人值班,Bob 下班
兩者 commit & 各自檢查都通過,合起來違反約束;兩者寫不同列,故 W–W 偵測抓不到。
Read skew & \(T_1\): 讀帳戶 A = 100
\(T_2\): A \(\to\) 50、B \(\to\) 150 轉帳並 commit
\(T_1\): 讀 B = 150 & \(T_1\) 看到總額 250,不一致的快照。
隔離等級
ANSI/ISO SQL-92 用「禁止哪些異常」定義四個等級:
| 等級 | Dirty read | Non-repeatable | Phantom | Write skew |
|---|---|---|---|---|
| Read Uncommitted | 可能 | 可能 | 可能 | 可能 |
| Read Committed | 可能 | 可能 | 可能 | |
| Repeatable Read | 可能 | 可能 | ||
| Serializable |
指出:ANSI 的定義是以鎖為心智模型寫的、對異常的描述有歧義、而且漏掉了 lost update、write skew 等現象。該文定義了 Snapshot Isolation(SI):每筆交易看到開始時刻的一致快照, 寫入時若發現同一列已被並行交易改過則中止(first-committer-wins)。SI 沒有 dirty/non-repeatable/phantom read, 卻允許 write skew——所以它不是 serializable,但 Oracle 把 SI 稱為「SERIALIZABLE」,PostgreSQL 9.1 之前亦然。
各系統的實際行為:
P2.1cmP3.0cmP4.3cmP4.3cm 系統 & 預設等級 & Repeatable Read 的實作 & Serializable 的實作
PostgreSQL & Read Committed(每個語句取新快照) & Snapshot Isolation:交易級快照;無 phantom;更新衝突報 & SSI(Serializable Snapshot Isolation,9.1+):偵測危險結構並中止,真正可序列化
MySQL InnoDB & Repeatable Read & MVCC 快照 + gap/next-key lock 防 phantom(僅對鎖定讀) & 全部讀加 S 鎖(2PL)
Oracle & Read Committed & 不提供(直接跳到 Serializable) & 其實是 SI,允許 write skew
SQL Server & Read Committed(鎖式;可開 RCSI) & 鎖式 & 鎖式 2PL;另有 SNAPSHOT 等級
CockroachDB / Spanner & Serializable & — & 預設即可序列化(分別為 SSI 變體與 2PL + TrueTime)
PostgreSQL 把 Read Uncommitted 當成 Read Committed 處理——MVCC 架構下根本不會讀到未 commit 的版本。
Snapshot Isolation 與 write skew
-- 兩個 session 同時執行
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT count(*) FROM doctors WHERE on_call; -- 兩邊都得到 2
UPDATE doctors SET on_call = false WHERE name = 'Alice'; -- session 1
UPDATE doctors SET on_call = false WHERE name = 'Bob'; -- session 2
COMMIT; -- 兩邊都成功:0 人值班!
兩筆交易寫的是不同列,first-committer-wins 不會發生衝突。防範方法: (1) 改用 ——SSI 會在其中一方 commit 時偵測到 rw-conflict 的危險結構並回報 ; (2) 把「隱含的衝突」物化: 鎖住所讀的值班列、或在一張 表上做 製造 W–W 衝突; (3) 用約束(如對「至少一人值班」建立觸發器或 exclusion constraint)。
在 REPEATABLE READ 與 SERIALIZABLE 下,PostgreSQL 可能以 SQLSTATE (或 deadlock)中止任何交易, 這不是錯誤,而是協定的一部分:應用程式應該 重試整筆交易(含重新讀取),通常搭配指數退避並限制次數。 框架常提供 類的工具。同時避免長交易:它們阻礙 VACUUM 回收舊版本(第 9 章), 也放大衝突機率; 可以自動砍掉忘記 commit 的連線。
並行控制的三大家族
接下來三章分別深入三種實現隔離性的策略,這裡先建立地圖:
P2.6cmP4.4cmP2.6cmP4.0cm 策略 & 機制 & 假設 & 代表
悲觀:兩階段鎖(2PL) & 讀前取 S 鎖、寫前取 X 鎖,commit 時一併釋放;需偵測死結 & 衝突常見 & SQL Server、DB2、InnoDB(寫入)
樂觀:OCC & 讀/寫在私有空間進行,commit 時驗證讀集沒被改過,失敗則重做 & 衝突罕見、交易短 & 記憶體資料庫(Hekaton、Silo)、FoundationDB
多版本:MVCC & 寫入產生新版本,讀者看快照永不阻塞寫者;搭配 2PL 或 SSI 處理寫衝突 & 讀多寫少 & PostgreSQL、Oracle、InnoDB(讀取)、CockroachDB
時間戳排序(T/O) & 依交易時間戳決定順序,違反者中止 & 理論基礎;分散式常用 & Spanner(TrueTime)、部分 NewSQL
每筆寫入交易取得一個 32 位元遞增的 XID(/); 唯讀交易不消耗 XID(只用虛擬 XID),這就是為何「唯讀為主」的負載較不會遇到 XID 回繞。 交易狀態(in progress / committed / aborted)存在 commit log(pg_xact,舊稱 clog),每個 XID 兩個 bit。 Savepoint 以子交易(subtransaction)實作,各有自己的 XID; 記錄父子關係。 超過 64 個子交易的交易會讓快照的子交易快取溢出,造成明顯的效能退化——ORM 的「每個語句一個 savepoint」設定要特別留意。
v4 加入 : 配置遞增的 txn id、 寫 commit 記錄並等 WAL 落盤、 依 undo 資訊回復。 初版隔離以表級 2PL 實現(簡單但正確),v5 換成 MVCC 快照。測試中應包含本章每一種異常的重現腳本, 確認在你宣稱的隔離等級下它們不會發生。
排程 \(R_1(A)\,R_2(B)\,W_1(B)\,W_2(A)\,C_1\,C_2\):畫優先圖,判定是否衝突可序列化;是否 recoverable?是否 strict?
寫出一個 view serializable 但非 conflict serializable 的排程(提示:blind write)。
在 Read Committed 下, 兩個並行執行會不會 lost update? 若改成應用程式先 再 呢?說明 PostgreSQL 在 RC 下對已被更新列的「重新檢查」行為。
為 write skew 例子設計三種不同的防護方式並比較它們對並行度的影響。
為什麼 PostgreSQL 的 Repeatable Read 沒有 phantom,而 ANSI 允許 RR 有 phantom?這代表 PostgreSQL 的 RR 比 ANSI 的 RR 強還是弱?
(動手)開兩個 ,在 RC、RR、SERIALIZABLE 下分別重現表格中的每一種異常,記錄哪些等級擋住了哪些異常。
H. Berenson, P. Bernstein, J. Gray, J. Melton, E. O’Neil, P. O’Neil, , SIGMOD 1995: https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tr-95-51.pdf
PostgreSQL 文件 Ch. 13 Concurrency Control(隔離等級、SSI 行為、明確鎖定):https://www.postgresql.org/docs/current/mvcc.html
PostgreSQL Wiki SSI(含值班醫師等範例):https://wiki.postgresql.org/wiki/SSI
M. Kleppmann, Hermitage: Testing the “I” in ACID(各資料庫隔離等級實測腳本):https://github.com/ept/hermitage
Jepsen, Consistency Models(隔離等級與一致性模型的統一地圖):https://jepsen.io/consistency
CMU 15-445 Fall 2025 Lecture #16 Concurrency Control Theory。
P. Bernstein, V. Hadzilacos, N. Goodman, Concurrency Control and Recovery in Database Systems(免費 PDF): https://www.microsoft.com/en-us/research/people/philbe/book/
J. Gray, A. Reuter, Transaction Processing: Concepts and Techniques, 1992(經典巨著)。
M. Kleppmann, Designing Data-Intensive Applications, 2nd ed., Ch. 8 Transactions。