Transactions are a daily reality in backend development. Order creation, payment processing, inventory deduction — operations grouped into a single “all succeed or all fail” unit. The four letters of ACID name those guarantees, but what each letter actually guarantees is often understood only at the surface.
C and I in particular are frequently misread. C gets simplified into “the DB handles consistency,” and I gets treated as a simple on/off. Each of the four properties has a line between what it guarantees and what it does not — that line is what this post draws.
A (Atomicity)
Either every operation in a transaction is applied, or none is. If something fails mid-way, changes up to that point are fully rolled back.
Take an order creation transaction: insert an order row, decrement inventory, record payment — three operations grouped in one transaction. If the payment record step errors out, the order row and inventory change are both undone. No partial state like “the order went in but payment didn’t” exists.
DBs typically implement this with an undo log. Each change records the prior value separately, and a rollback restores from that log. Once commit completes, the undo log becomes irrelevant.
The atomicity discussed here is limited to a single DB. Atomicity across distributed systems — multiple DBs or external services — requires separate mechanisms like 2PC or saga and is outside this series.
C (Consistency)
The most frequently misunderstood property. “The DB handles consistency” is only half correct.
What C guarantees is the DB constraint layer — primary key uniqueness, foreign key referential integrity, check constraints, NOT NULL, unique indexes. If any of these are violated at commit time, the DB refuses the commit. This part is automatic.
Application invariants are a different story. “The sum of order amounts must equal the payment amount,” “inventory cannot go negative,” “a refund requires the original transaction to be in ‘paid’ state” — business rules like these often cannot be expressed as DB constraints, or only partially.
Preventing negative inventory works with CHECK (stock >= 0), but rules like “is this refundable?” that combine multiple rows and states fall to application code. How these rules are validated within a transaction, and in what order, is the application’s responsibility.
The ‘C’ in ACID means transition to a consistent state. What “consistent” means is defined jointly by DB constraints and application invariants, and the DB enforces only the constraint portion. Without drawing this line clearly, bugs of the form “I thought the DB was handling it” emerge.
I (Isolation)
I controls what concurrent transactions can see of each other. When transaction T1 is mid-execution and T2 touches the same data, how much of each other’s intermediate state is visible.
Perfect isolation — every transaction behaving as if executed serially — is the strongest correctness guarantee available. But that guarantee nearly kills concurrency. If only one transaction can run at a time, throughput collapses.
So RDBs offer isolation in levels, not as a single on/off switch. The choice is “which anomalies to permit.” Stronger isolation permits fewer anomalies but costs more concurrency.
The point of I is why those levels exist. Correctness and concurrency are in direct tension, and the next post in this series takes those four levels apart along with the anomalies each one blocks.
D (Durability)
Once a transaction commits, its result survives system failure. Power loss, OS crash, process kill — committed data is not lost.
The typical implementation is a Write-Ahead Log (WAL). Changes are written to a separate log file and fsync’d to disk before the main data file. After restart, replaying the log restores the committed state.
D guarantees durability at commit time. Failures before commit mean the transaction was never applied, which connects naturally to A. Together, the two guarantees leave only two extremes: “definitely complete” or “as if never happened.”
The internals of WAL — checkpoints, log recycling, recovery algorithms — are a substantial topic in their own right, and this series stops at the conceptual level of what D guarantees.
Summary
A and D are relatively simple guarantees. All applied or none applied (A). Permanent after commit (D). The implementations are complex, but the guarantees themselves are clear.
C has shared responsibility. DB constraints cover one side, application invariants cover the other. Missing this line leads to bugs where rules thought to be guaranteed by the transaction turn out not to be.
I is a policy choice. Perfect isolation is expensive, weaker isolation permits anomalies. The reason four levels exist — the tension between correctness and concurrency — is the through-line of this whole series.
The next post takes those four levels and shows which anomalies each one blocks and which it allows.