The Runtime Theory
ApplicationInternalsstorage

What happens when a transaction rolls back?

A step-by-step walk from ROLLBACK or error, through aborted-state marking, lock release, undo (InnoDB) vs no-undo (Postgres), WAL records, savepoints, and the client error.

The Runtime Theory Team4 min read07 steps

layer stack

Application

HWHardware
KKernel
RTRuntime
APPApplication
SYSSystem
CLIClient
NETNetwork
TLSCrypto
SRVServer

adjacent altitudes in this subsystem are still being traced

trace spine

  1. 01 Abort trigger: ROLLBACK, error, or deadlock
  2. 02 Transaction marked aborted
  3. 03 Locks released
  4. 04 Undo: InnoDB rolls pages back
  5. 05 No-undo: Postgres invalidates, vacuum reclaims
  6. 06 WAL abort record and buffer pool state
  7. 07 Savepoints: ROLLBACK TO SAVEPOINT
On this page

Every transaction has two endings, and only one gets celebrated. ROLLBACK is the un-celebrated one — but it's the one the engine must make bulletproof, because it's the system's escape hatch for errors, deadlocks, and constraint violations. The interesting asymmetry: two major engines implement abort in completely different ways, and both are correct. The difference is when the work gets undone: at abort time (InnoDB) or never, just invisible (Postgres).

Step 1 — the trigger

Abort starts one of three ways: an explicit ROLLBACK;, an error inside a statement (constraint violation, division by zero, 40P01 deadlock from the victim path), or a statement that can't continue (25P02 — "current transaction is aborted, commands ignored until end of transaction block" — the infamous "the whole transaction is now radioactive" state). From the engine's perspective they're identical: the transaction is going to die, and its work must never be observable.

Step 2 — the state flip

Postgres marks the transaction aborted in the commit status log (CLOG): xact_status = ABORTED. From this instant, the MVCC rule "committed before my snapshot" is permanent: no snapshot will ever see this transaction's rows, because its XID's status is aborted, forever. InnoDB instead starts actively erasing: it walks the transaction's undo log entries (each page modification was recorded with its "before image") and restores pages to their pre-transaction state, in reverse order.

Step 3 — locks released

All locks held by the transaction are released at abort (row locks, tuple locks, table locks, advisory locks — every waiter in the lock queues wakes up). This is the moment the deadlock survivor proceeds and the rest of the workload unblocks. Lock release is the cheapest and most important part of abort — it's why abort latency matters to everyone else, not just the aborted client.

Step 4 — the undo pass (InnoDB)

InnoDB's rollback: for each undo record (in reverse order — last change first), fetch the affected page from the buffer pool, restore the before-image bytes, and log the restoration to the redo log (crash safety: if power dies mid-rollback, recovery finishes it — the undo pass itself is redo-logged). Cost: proportional to the pages touched. A transaction that modified 10,000 pages takes 10,000 page-fetch-and-restore operations — abort is a real cost, not a flag flip. This is why giant single transactions are dangerous: their rollback is a giant operation too.

Step 5 — the no-undo path (Postgres)

Postgres deliberately doesn't do this. The changes are still sitting in the heap pages — visible to nobody, since the CLOG says aborted. What cleans them up? Vacuum (see the vacuum trace): dead tuples get reclaimed when the global horizon moves past the abort. The transaction's rows linger as garbage until then — which is why an aborted write-heavy transaction still causes bloat in Postgres, and why rollback there is O(1) while InnoDB's is O(work). Same correctness, radically different cost curve — and both engines tune differently because of it (Postgres: vacuum cadence; InnoDB: undo tablespace size).

Step 6 — WAL and the buffer pool

Both engines write the abort outcome to the log: Postgres appends the abort's status change to the WAL (durable in the next flush), InnoDB's undo/redo combination makes the rollback durable. Buffer-pool pages: InnoDB's restored pages are marked dirty and flushed normally; Postgres's aborted-tuple pages are just dirty. Neither fsyncs anything extra at abort — crash safety was already guaranteed by the logging rules; abort is just more log records.

Step 7 — savepoints: abort in miniature

ROLLBACK TO SAVEPOINT s1 doesn't abort the transaction — it creates a subtransaction (Postgres: a subxid; InnoDB: a savepoint in the undo log). The machinery is identical but scoped: the subtransaction's changes are undone (or marked invisible), its locks released, and the parent transaction continues. Cost: subtransactions add CLOG entries and undo bookkeeping — thousands of savepoints in a loop is a real overhead. COMMIT inside a savepoint isn't a thing — savepoints are purely abort scopes.

What it costs

  • Postgres abort: ~µs — state flip + lock release. The deferred cost lands on vacuum and bloat.
  • InnoDB abort: proportional to modified pages — 1-10µs per page typically, plus redo logging of the undo.
  • Both: any waiter's latency, from lock release to wakeup.
  • The client side: the error code is the contract — 40P01 (deadlock), 23505 (unique violation), 40001 (serialization failure) are the retryable set; everything else is yours.
sql
BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;   -- succeeds
INSERT INTO accounts_log VALUES (1, -1000);                   -- violates a CHECK constraint
ERROR:  new row for relation "accounts_log" violates check constraint "positive_balance"
ROLLBACK;                                                      -- the whole block is discarded

The pattern to internalize: the engine never half-applies a transaction. Whatever the failure, whatever the engine, the visible result is nothing happened — either by active undoing or by permanent invisibility. Your job is only to retry the retryable error classes and design the app so that retries are safe. Abort is the system keeping its promise; bloat and undo cost are the bill for that promise.