分散式交易與兩階段提交 (2PC)

★★★★Reviewed

FromDDIA

📚 From the Books

分散式交易與兩階段提交 (2PC)Consensus

讓一筆交易橫跨多個節點/系統,要嘛全部提交、要嘛全部回滾。2PC 是最經典的原子提交協定——但它的致命傷是 coordinator 一掛,參與者就阻塞在原地。

🧠 Intuition

TIP

把它想成婚禮的「我願意」。司儀(coordinator)先問每位參與者「你準備好了嗎?」——大家一旦回答 yes 就不能反悔;等所有人都 yes,司儀才宣布「成立」。只要有一人 no 或沒回,全部作廢。

⚖️ Tradeoffs

兩個階段 — prepare 與 commit
  • Phase 1 (prepare / voting):coordinator 問所有 participant「能否提交?」participant 把資料寫到持久化、鎖好,回 yes/no。回 yes = 承諾「之後叫我 commit 我一定能 commit」。
  • Phase 2 (commit / abort):全部 yes → coordinator 寫下 commit 決定 → 通知大家 commit;任一 no → 通知大家 abort。
  • coordinator 把決定寫進自己的 log 那一刻就是不可逆的全域 commit point
阻塞問題 — coordinator 是單點
  • 致命窗口:participant 已回 yes、但還沒收到最終決定時 coordinator 崩潰 → participant 既不能 commit 也不能 abort,只能持有鎖無限期等待 (in-doubt)。
  • 這段期間相關資料被鎖住,會拖垮其他交易——這是 2PC 被詬病的核心。
  • 緩解但無法根除:coordinator 寫 WAL 後重啟可恢復決定;但若 coordinator 永久死亡,participant 仍卡死。3PC 想解阻塞,但對網路假設太強,實務罕見。
與共識的差異 — 為什麼 2PC 不是共識
  • 共識(Raft/Paxos)只要多數派存活就能繼續推進並容錯;2PC 要求所有 participant 都 yes 才 commit,且全靠單一 coordinator 做決定——一票否決 + 單點。
  • 因此 2PC 不容錯:任一參與者或 coordinator 故障都可能阻塞,無法像共識那樣繞過少數故障節點。
  • 進階做法:把 coordinator 的決定 log 本身用共識複製(讓 coordinator 高可用),結合兩者之長。
XA 與實務替代 — Saga / Outbox
  • XA 是 2PC 的工業標準介面(橫跨 DB、訊息佇列),但繼承所有阻塞與運維痛點,且跨異質系統時鎖與超時難協調。
  • Saga:拆成一串本地交易 + 補償交易 (compensating transaction),放棄原子隔離換可用性與低耦合;只達成最終一致。
  • Outbox / 事件溯源:在本地交易內把「待發事件」寫進同庫的 outbox 表,再由獨立程序可靠投遞——避免「寫庫 + 發訊息」這種跨系統雙寫。
  • 趨勢:微服務多半避開 2PC/XA,改用 saga + outbox 把分散式原子性退化成可補償的最終一致。

🔑 Takeaways

✍️ My Notes

2026-07-01

  • Q: 既然 Raft 那種共識能容錯,為什麼不直接拿共識取代 2PC?
  • A: 它們解的是不同問題。共識是「N 個節點對同一個值/順序達成一致」,只要多數派同意即可,天生容錯。2PC 是「M 個異質參與者各自本地交易的原子提交達成一致」,語義上要全體一致同意(任一參與者說不行就得全 abort,因為它可能違反本地約束)——這種「全員一票否決」本質上無法只靠多數派。所以共識不能直接取代 2PC。但兩者可結合:把 2PC 那個脆弱的單點 coordinator 換成一組用共識複製決定 log 的高可用 coordinator,這樣參與者就不會因 coordinator 單機崩潰而永久 in-doubt——這正是現代分散式 DB(如 Spanner)的做法。

📖 Further Reading

🔗 Dive Deeper