← All articles

Backend & Data

ACID transactions and isolation levels

2026-07-26 · 6 min read

Wrap some database operations in a transaction and you're leaning on a four-letter promise: ACID. Atomicity, Consistency, Isolation, Durability. Three of them are intuitive. The fourth — Isolation — is where the subtle, expensive bugs live, because it governs what happens when two transactions touch the same data at the same time.

The four letters

  • Atomicity — all of it happens, or none of it does. Transfer money and the debit succeeds but the credit fails? The whole thing rolls back. No half-transfers.
  • Consistency — the transaction moves the database from one valid state to another; rules and constraints hold before and after.
  • Isolation — concurrent transactions don't step on each other; each behaves as if it ran alone.
  • Durability — once committed, it survives — even if the server loses power a second later.

Why isolation is the hard one

Atomicity and durability are mostly the database's job. Isolation is where you get to choose how careful to be — and cutting corners for speed is how the classic anomalies sneak in. The canonical one is the lost update: two transactions read the same value, both modify it, and the second write silently erases the first.

A lost update without isolation T1 (add 20) T2 (subtract 10) time read 100 read 100 write 120 write 90 final 90 (should be 110) T1's +20 lost
Both transactions read 100 before either wrote. T2's write lands last and overwrites T1 — the +20 vanishes. Stronger isolation forbids this interleaving.

Isolation levels — the safety/speed dial

Databases let you pick how strict isolation is, trading correctness against concurrency:

  • Read Uncommitted — can even read another transaction's uncommitted, soon-to-be-rolled-back changes (a "dirty read"). Fast, dangerous, rarely wanted.
  • Read Committed — only see committed data. A common default; still allows some anomalies.
  • Repeatable Read — a row you read won't change under you mid-transaction.
  • Serializable — the gold standard: transactions behave as if run one at a time, in some order. No anomalies — at the cost of more locking/retries.
The gist

A transaction promises all-or-nothing, and — if you ask for enough isolation — that concurrent transactions won't corrupt each other. Weaker isolation is faster but lets two correct programs quietly lose each other's writes.

What to actually do

For anything where being wrong costs real money — balances, inventory, counters — use strong isolation, or make the update atomic (UPDATE … SET balance = balance + 20 instead of read-modify-write in your app). It's the database-level sibling of idempotency: both are about staying correct when things happen concurrently or more than once. And it connects to CAP — strong guarantees cost availability under load and partitions. Know which anomalies your app can't tolerate, and buy exactly enough isolation to prevent them.


ACIDTransactionsDatabasesBackend
← Back to the blog