Two-Phase Commit Explained: Coordinating Atomic Writes Across Multiple Databases
Moving money between two accounts held in two different databases requires both writes to succeed or both to be rolled back. Two-phase commit is the classic protocol for getting that all-or-nothing guarantee across machines that cannot see each other's internal state.
Published July 6, 2026The Problem: Atomicity Across Independent Resources
A single database gives you atomicity for free through its transaction log — either the whole transaction is durably written or none of it is. The moment a transaction spans two separate resource managers, that guarantee is not automatic. If database A commits its half and database B then crashes before committing its half, the system is left in an inconsistent state with no local mechanism to detect or fix it.
Two-phase commit (2PC) introduces a coordinator process whose only job is to make sure every participant agrees on the same outcome, commit or abort, even though none of them can directly observe what the others are doing.
Phase 1: Prepare (Voting)
The coordinator sends a prepare message to every participant. Each participant does everything necessary to guarantee it can commit if told to — it writes the transaction to its own durable log, acquires the locks it needs, and checks for any local reason the write would fail. It then replies yes (ready to commit) or no (must abort). Crucially, once a participant votes yes, it has made a durable promise: it must be able to commit later even if it crashes and restarts in the meantime, which is why the prepared state has to be written to persistent storage before the vote is sent.
Phase 2: Commit or Abort (Decision)
If every participant voted yes, the coordinator writes the decision to its own log and sends commit to everyone. If any participant voted no, or timed out, the coordinator sends abort to everyone instead. Each participant applies the decision, releases its locks, and acknowledges back to the coordinator. Only after every acknowledgment does the coordinator consider the transaction finished.
The Blocking Problem
2PC's well-known weakness shows up when the coordinator fails after collecting votes but before broadcasting the decision. A participant that voted yes is now stuck: it has promised to commit if told to, so it cannot unilaterally abort and release its locks, but it also does not know whether the coordinator decided to commit or abort before crashing. It has to hold its locks and wait for the coordinator to recover, which can block other transactions indefinitely. Three-phase commit adds an extra round to remove this blocking window under certain failure assumptions, but it is rarely deployed in practice because it does not tolerate network partitions well.
Where Two-Phase Commit Is Still Used
- XA transactions: The X/Open XA specification standardizes the interface between a transaction manager and resource managers (databases, message queues) so 2PC can coordinate across heterogeneous systems. Java's JTA implements this interface directly.
- Distributed SQL databases: Systems like CockroachDB and Google Spanner use 2PC-style coordination internally for transactions that span multiple storage ranges, layered under a consensus protocol that handles the coordinator-failure problem.
- Message brokers with transactional outbox patterns: Coordinating a database write with a message publish sometimes uses a lightweight two-phase approach rather than full XA, trading strict atomicity for simpler operational behavior.
Modern microservice architectures more often reach for the Saga pattern instead of 2PC: a sequence of local transactions, each with a compensating action to undo it if a later step fails. Sagas give up strict atomicity and isolation in exchange for not holding cross-service locks, which matters a lot when the participants are services you do not fully control and cannot force to block.
Conclusion
Two-phase commit is a small protocol with an outsized influence on how distributed transactions are taught, because it makes the fundamental trade-off explicit: you can get strict atomicity across independent resources, but only by accepting a coordinator single point of failure and a blocking window during recovery. Understanding where that blocking window comes from is the real payoff of studying 2PC, even if the systems you build end up choosing sagas or a consensus-backed alternative instead.
The X/Open XA specification is the formal document most production XA implementations trace back to, and it is worth skimming if you need to know exactly what a resource manager is required to guarantee once it votes yes.