Atomicity, Consistency, Isolation, Durability — why they matter and how databases enforce them.
Published September 21, 2026
A transaction groups several database operations into a single logical unit of work. Transferring ₹500 from Asha to Vikram means debiting one account, crediting another, and recording the transfer. If the system crashes, or another transfer runs at the same time, we need guarantees that the data still makes sense. ACID names those guarantees:
BEGIN;
UPDATE accounts SET balance = balance - 500 WHERE id = 'asha' AND balance >= 500;
-- the application checks that exactly 1 row was updated; if not → ROLLBACK
UPDATE accounts SET balance = balance + 500 WHERE id = 'vikram';
INSERT INTO transfers (from_id, to_id, amount) VALUES ('asha', 'vikram', 500);
COMMIT;
Either every operation in the transaction takes effect, or none do. If the credit fails, or the server crashes after the debit, the debit is undone. There's never a state where money left one account and arrived nowhere.
How databases achieve it:
ROLLBACK restore the previous state.Atomicity is about failure, not concurrency: "the transaction happened completely or not at all."
A transaction moves the database from one valid state to another. Valid means every rule holds: constraints (NOT NULL, UNIQUE, foreign keys, CHECK (balance >= 0)), plus the application's own invariants (the total money across accounts is unchanged by a transfer).
The database enforces the declared constraints, and rejects a transaction that would violate them. But invariants that only exist in application logic are the application's responsibility. Consistency is therefore partly a property of the database, and partly of correctly written transactions.
Don't confuse this "C" with the "C" in the CAP theorem, which means all replicas agreeing (linearizability). See CAP Theorem.
Many transactions run simultaneously. Isolation controls how much they can see of each other's uncommitted or in-progress work. Ideally, each would behave as if it ran alone (serializable), but full isolation costs performance, so databases offer weaker levels.
The anomalies that weaker levels allow:
| Anomaly | What happens |
|---|---|
| Dirty read | Reading another transaction's uncommitted change, which may later be rolled back |
| Non-repeatable read | Reading the same row twice in one transaction, and getting different values because another transaction committed in between |
| Phantom read | Re-running a query and getting different rows (new matching rows were inserted) |
| Lost update | Two transactions read, modify and write the same value, and one overwrites the other |
| Write skew | Two transactions each check a condition, then write different rows, together breaking the rule. Example: two doctors each see "another doctor is on call" and both go off call |
| Level | Prevents | Typical default in |
|---|---|---|
| Read Uncommitted | Almost nothing | Rarely used |
| Read Committed | Dirty reads | PostgreSQL, Oracle, SQL Server |
| Repeatable Read | + non-repeatable reads (and phantoms in PostgreSQL and InnoDB for consistent reads) | MySQL InnoDB |
| Serializable | All anomalies, including write skew | Opt-in |
Implementations differ:
SELECT … FOR UPDATE) serialize conflicting writes.The details and practical choices are in Transactions & Isolation Levels.
Once COMMIT returns successfully, the change survives crashes and power loss.
How it's achieved:
fsync) before the commit is acknowledged.Durability can be relaxed for speed, for example PostgreSQL's synchronous_commit = off, or asynchronous replication. That accepts a risk of losing the most recent commits in exchange for lower latency. It's reasonable for some data, such as analytics events, and not for payments.
Q: What's the difference between atomicity and isolation? A: Atomicity concerns failure: a transaction's changes happen completely or not at all. Isolation concerns concurrency: how and when a transaction sees other transactions' changes while they run at the same time.
Q: How do databases guarantee atomicity and durability after a crash? A: With write-ahead logging. Changes are written to a durable log before being applied. On restart, the database replays the log, redoing committed transactions and rolling back incomplete ones. Commits are acknowledged only after the log is flushed.
Q: Why not always use Serializable isolation? A: It reduces concurrency. Depending on the implementation, it means more locking, or more transaction aborts that must be retried. Many workloads are safe under Read Committed with targeted techniques (row locks, atomic updates, constraints), and use Serializable only where anomalies like write skew are possible.
Q: How do you prevent lost updates without Serializable?
A: Use an atomic update (SET balance = balance - 500), lock the row first (SELECT … FOR UPDATE), or use optimistic locking with a version column (UPDATE … WHERE id = ? AND version = ?, retrying if 0 rows are updated).
Q: How do you get ACID-like guarantees across microservices? A: You generally don't get a single ACID transaction. Use sagas with compensating actions for multi-step workflows, the transactional outbox pattern for reliable event publishing, and idempotent consumers. Accept eventual consistency between services, and keep invariants that need strong guarantees within one service's database.