Skip to main content

Transactions & ACID

Overview

A transaction groups multiple reads/writes into a single logical unit of work. ACID — Atomicity, Consistency, Isolation, Durability — is the set of guarantees a transactional database makes about how that unit of work behaves, especially when things go wrong (crashes) or when other transactions run concurrently. Each letter protects against a specific, concrete failure mode; understanding what breaks without each one is the fastest way to understand what it actually buys you.

Core Concepts

TermMeaningWhat breaks without it
AtomicityA transaction's writes either all happen, or none do.A transfer that debits one account but crashes before crediting the other — money disappears.
ConsistencyA transaction only moves the database from one valid state to another, per application-defined invariants (constraints, triggers).A CHECK (balance >= 0) constraint being violated because a transaction partially applied.
IsolationConcurrent transactions appear to run as if executed one at a time.Two transactions both read a counter as 5, both increment it, both write 6 — one increment is lost.
DurabilityOnce a transaction commits, its effects survive a crash immediately after.The database acknowledges a commit, the power fails, and the write is gone when it restarts.

Architecture / Mechanism

Atomicity and durability are typically implemented together via a write-ahead log (WAL): changes are appended to a durable log before being applied to the actual data pages, and before a commit is acknowledged, its log entries must be flushed to durable storage. If the database crashes mid-write, it replays the WAL on restart to redo committed work and undo uncommitted work — atomic all-or-nothing recovery.

Isolation levels and the anomalies they prevent

The ANSI SQL standard defines isolation levels in terms of three concurrency anomalies they may or may not permit:

  • Dirty read: reading a row another transaction has written but not yet committed.
  • Non-repeatable read: re-reading the same row within a transaction and getting a different value, because another transaction committed a change to it in between.
  • Phantom read: re-running the same range query within a transaction and seeing a different set of rows, because another transaction inserted/deleted a matching row in between.
Isolation levelDirty readNon-repeatable readPhantom read
Read UncommittedPossiblePossiblePossible
Read CommittedPreventedPossiblePossible
Repeatable ReadPreventedPreventedPossible
SerializablePreventedPreventedPrevented
Standard vs. real implementations

The ANSI SQL definitions describe permitted phenomena, not a specific mechanism — real databases vary. PostgreSQL's "Repeatable Read", for example, is implemented via snapshot isolation and in practice also prevents phantom reads, which is stricter than the ANSI SQL requirement. Always check a specific database's documentation rather than assuming the table above applies exactly.

Locking vs. MVCC

Isolation can be implemented in (at least) two different ways:

  • Locking: transactions take locks on rows (or ranges) they read/write; conflicting transactions block or abort. Simple to reason about, but readers and writers can contend directly, and range/predicate locks are needed to prevent phantoms.
  • MVCC (Multi-Version Concurrency Control): the database keeps multiple versions of a row and gives each transaction a consistent snapshot to read from, so readers never block writers and writers never block readers. PostgreSQL, MySQL/InnoDB, and Oracle all use MVCC as their primary isolation mechanism, typically combined with row-level locking for write-write conflicts.

Practical Usage

BEGIN;

-- Both statements succeed together, or neither is applied (atomicity).
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

COMMIT; -- durable once this returns
-- Setting an explicit isolation level (syntax varies by database)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT balance FROM accounts WHERE id = 1;
-- ... application logic decides how much to transfer ...
UPDATE accounts SET balance = balance - 50 WHERE id = 1;
COMMIT;

Edge Cases & Pitfalls

Lost updates

Read Committed does not prevent a "read-modify-write" race: two transactions can both read a balance of 100, both compute "100 - 50 = 50" in application code, and both write 50 back — one transaction's decrement is silently lost. Preventing this needs either Repeatable Read/Serializable, or an explicit SELECT ... FOR UPDATE row lock, or an atomic UPDATE accounts SET balance = balance - 50 that never reads the value into application code at all.

  • Higher isolation levels aren't free: Serializable typically means more aborted/retried transactions under contention, or more locking overhead — a real throughput cost for the stronger guarantee.
  • "Consistency" in ACID (application-defined invariants) is a different concept from "Consistency" in the CAP theorem (all nodes agree on the latest value) — the same word means two different things in these two contexts, a frequent source of confusion.

Comparisons

MechanismReaders block writers?Writers block readers?Typical use
Pessimistic lockingSometimes (shared/exclusive locks)YesStrong conflict environments, simple reasoning
MVCCNo (reads a snapshot)NoPostgreSQL, MySQL/InnoDB, Oracle — most modern OLTP databases

References

  • ISO/IEC 9075 (SQL) — defines the standard isolation levels and anomalies.
  • Berenson et al., "A Critique of ANSI SQL Isolation Levels" (SIGMOD 1995) — clarifies ambiguities in the standard's definitions and introduces Snapshot Isolation.

Books & Videos

  • Martin Kleppmann, Designing Data-Intensive Applications, Ch. 7 "Transactions" — isolation levels, anomalies, and how they're actually implemented.
  • Silberschatz, Korth, Sudarshan, Database System Concepts — the transactions and concurrency control chapters.