ACID Properties
A bank must move 500 vanishes into thin air. Two such transfers might also run concurrently. If their reads and writes interleave carelessly, the bank's books can become permanently inconsistent. This can happen even with no crash at all. ACID properties are the contract a database management system offers. They ensure programmers never have to reason about these failure modes by hand. This problem is not exotic. It is the default hazard of any system where multiple operations must appear to happen "all at once." This holds whenever hardware, memory, and disks are shared with other work. Every serious data-management system must answer the same question. This includes a single-node relational database and a distributed ledger alike. What happens when a multi-step operation is interrupted? What happens when it runs alongside another one? What happens when the machine loses power mid-write? The Transaction Concept names the unit of work. ACID is the precise specification of what a database promises about that unit. The big picture is that ACID decomposes "correct behavior under failure and concurrency." It splits this into four independent, checkable guarantees. Atomicity means all-or-nothing execution. Consistency means invariants are preserved. Isolation means concurrent transactions don't corrupt each other's view of the world. Durability means committed work survives crashes. Each letter addresses a different threat. These are, respectively: a crash mid-transaction, a buggy or malicious update, a scheduler interleaving two transactions, and a power failure right after commit. Each is implemented by a different piece of database machinery. This article defines each property precisely. It shows the mechanisms real systems use to deliver them: logging, locking, recovery. It works through a full bank-transfer trace, including a simulated crash. It also previews how the deeper machinery actually implements these guarantees. This includes Write-Ahead Logging for durability/atomicity. It also includes Serializability and Schedules for isolation.
- ACID = Atomicity, Consistency, Isolation, Durability — four guarantees a DBMS gives every transaction.
- Atomicity: a transaction's effects are all applied or none are, even if the system crashes midway.
- Consistency: a transaction moves the database between states that satisfy its integrity constraints.
- Isolation: concurrent transactions behave as if run one at a time, in some order.
- Durability: once a transaction commits, its effects survive any subsequent crash.
- Atomicity and durability are implemented mainly via logging and recovery; isolation via concurrency control (locking or timestamps).
ACID turns "what if the system crashes or another transaction interferes" into a solved problem, so application code can simply say begin, do work, commit.
The idea
A transaction is a sequence of one or more database operations (reads and writes) that the application treats as a single logical unit — for example, "transfer $500 from Alice to Bob." The ACID properties are the guarantees the DBMS makes about how transactions behave, individually and in combination with each other and with failures.
Atomicity means a transaction is indivisible: either every one of its writes is applied to the database, or none are. There is no state in which the debit from Alice happened but the credit to Bob did not. Atomicity is what makes it safe to write multi-step logic without manually checking, after every crash, which steps completed.
Consistency means a transaction, run alone starting from a state that satisfies the database's integrity constraints (declared constraints like foreign keys and check constraints, plus any invariants the application relies on, such as "total money in the system is constant"), leaves the database in a state that again satisfies those constraints. Consistency is a joint responsibility: the DBMS enforces declared constraints automatically, but application logic must be written correctly for constraints that are not directly expressible in schema declarations, such as "the sum of all balances equals the sum of all deposits ever made."
Isolation means that even though many transactions may execute concurrently for performance, each transaction should behave as though it were the only one running. Formally, a concurrent execution (a schedule) should have the same effect as some serial (one-at-a-time) execution of the same transactions — this property is called <a class="wiki-link" href="/wiki/cs-db-serializability-schedules" data-track="wiki_article.body.wiki_link">serializability</a>. Isolation is what prevents one transaction's half-finished work from ever being visible to, or corrupted by, another.
Durability means that once a transaction commits — the DBMS has reported success to the application — its effects are permanent. Even a crash one instruction later, or a disk failure the next day, must not undo a committed transaction (subject to the assumption that the storage medium itself isn't destroyed). These four properties are logically independent axes, but the mechanisms that implement them overlap: the same Write-Ahead Logging infrastructure used for atomicity (undoing incomplete transactions) is also used for durability (redoing committed ones after a crash), while isolation is a separate concern handled by concurrency control.
How it works
Atomicity and durability: logging and recovery
The standard technique is write-ahead logging (WAL): before any change is made to the actual database, a log record describing the change is written to stable storage. The log records enough information to redo the change (if the transaction commits) or undo it (if the transaction aborts or the system crashes before commit). The essential rule, called the WAL rule, is: 1. Before a data page is written to disk, all log records describing changes to that page must already be on stable storage (write log first, ahead of data). 2. Before a transaction is considered committed, all its log records — including a special commit record — must be on stable storage. This ordering is what makes recovery possible after a crash: the log is a durable, ordered history of every intended change, so the recovery process can replay it to reconstruct exactly which transactions finished and which did not. ``` procedure COMMIT(T): write log record flush log to stable storage // durability boundary mark T as committed return success to caller procedure ABORT(T): for each log record of T, in reverse order: restore X to old_value // undo write log record flush log to stable storage procedure RECOVER(): // runs after a crash, on restart // Phase 1: analyze the log to find which transactions committed committed := {}, active := {} for each log record in the log, in order: if record is : committed.add(T); active.remove(T) if record is : active.remove(T) if record is : active.add(T) // Phase 2: redo all writes of committed transactions for each log record in the log, in order: if T in committed: write new to X // Phase 3: undo all writes of transactions that never committed for each log record in the log, in reverse order: if T in active: // never committed, never explicitly aborted write old to X ``` This "redo committed, undo uncommitted" scheme is what real systems (with further refinements such as checkpoints and the ARIES algorithm) implement; the full mechanics are the subject of Write-Ahead Logging. The key point for ACID is that atomicity (undo) and durability (redo) are two sides of the same log.
Isolation: concurrency control
Isolation is enforced by a concurrency-control protocol that regulates the interleaving of operations from different transactions. The two dominant families are:
- Lock-based protocols, such as two-phase locking, in which a transaction must acquire a shared (read) or exclusive (write) lock on a data item before accessing it, and — in the "two-phase" discipline — releases no lock until it has acquired all the locks it will ever need.
- Timestamp-ordering / multiversion protocols, in which transactions are ordered by timestamps and the system either enforces that ordering directly on reads/writes, or keeps multiple versions of each data item so readers never block writers (multiversion concurrency control, used by PostgreSQL, Oracle, and others).
``` procedure TWO_PHASE_LOCKING(T): growing_phase := true locks_held := {} procedure READ(X): if growing_phase: acquire shared-lock(X); locks_held.add(X) return value of X procedure WRITE(X, v): if growing_phase: acquire exclusive-lock(X); locks_held.add(X) X := v procedure COMMIT_OR_ABORT(): growing_phase := false // shrinking phase begins release all locks in locks_held ``` The isolation guarantee delivered is precisely: any schedule produced by a protocol satisfying two-phase locking is guaranteed conflict-serializable — equivalent in effect to some serial order of the transactions. This is the formal notion developed fully under Serializability and Schedules; here it suffices to know that isolation is achieved by restricting which interleavings are allowed, not by literally running transactions one at a time (which would be correct but far too slow). Real systems also expose weaker isolation levels — Read Uncommitted, Read Committed, Repeatable Read, Serializable — trading some isolation guarantees for concurrency and performance; "full" isolation (serializability) is the strongest and most expensive.
Consistency
Consistency is enforced partly automatically and partly by discipline:
- The DBMS checks declared constraints (primary keys, foreign keys, `CHECK` constraints, `NOT NULL`) at the end of each transaction (or statement) and aborts the transaction if any is violated.
- The application programmer is responsible for ensuring that, in isolation, each transaction's logic preserves invariants not captured by declared constraints.
Because atomicity and isolation are what make "checking constraints only at transaction boundaries" meaningful (intermediate, possibly-inconsistent states are never exposed to other transactions and never persist if the transaction aborts), consistency is often described as a consequence of the other three properties working correctly, combined with correct application logic.
Complexity & correctness
Atomicity / durability cost. Every write to a data item requires one additional sequential write to the log before (or, in practice, batched with) the corresponding data write. Because the log is append-only, log writes are sequential I/O, which is cheap relative to random-access data-page writes; typical systems amortize this cost by flushing the log in batches (group commit). The space overhead is one log record per write, each of size proportional to the changed data (or, with logical logging, proportional to the operation), so total log size is across the system's history until old records are discarded at a checkpoint.
Correctness of recovery. The WAL rule is the invariant that makes the redo/undo recovery procedure correct:
- Claim: after `RECOVER()` completes, the database state is exactly the state that would result from applying every committed transaction's writes, in log order, to the pre-transaction state, with no trace of any transaction that never committed.
- Argument: by the WAL rule, every log record for a transaction is durable before that transaction's commit record is durable, and every commit record is durable before the transaction is reported as committed. Hence, at any crash point, the log contains a prefix of true history that includes complete information for every transaction the outside world has been told committed. The analysis phase correctly partitions transactions into "committed" (has a commit record) and "never committed" (does not), because a transaction cannot be told it committed without its commit record being durable. Redo re-establishes every committed write (idempotent, since redoing an already-applied write is harmless if writes are applied in log order), and undo removes every write made by a transaction with no commit record, restoring the pre-transaction value. No transaction's effects are partially present in the final state, which is exactly atomicity plus durability.
Isolation cost and correctness. Two-phase locking's correctness argument: suppose two transactions and both hold their final lock release points and respectively (end of growing phase). If 's lock point precedes 's, one can show by induction on conflicting operations that every conflicting pair of operations between and occurs in the order " before ," which is precisely the condition for the schedule to be conflict-equivalent to the serial schedule — this is the formal content covered under Serializability and Schedules. The cost is concurrency lost to blocking (a transaction may wait for locks) and the possibility of deadlock, requiring detection or prevention, typically amortized overhead per lock request plus periodic deadlock-detection graph traversals in systems that use detection.
Honest tradeoffs. Full serializability and full durability are the strongest and most expensive settings; virtually every production system offers weaker, faster alternatives — weaker isolation levels (Read Committed is the common default), asynchronous replication instead of synchronous durability, and eventual consistency in distributed systems (see the CAP-theorem tradeoff space) — because the ACID guarantees, while easy to reason about, are not free.
Worked example
Trace the bank transfer : "transfer $500 from Alice (balance 1000) to Bob (balance 300)," alongside a concurrent transaction : "read Alice's balance for a statement," with a crash inserted mid-way.
| Step | Action | Log (stable storage) | Alice (disk) | Bob (disk) | Notes |
|---|---|---|---|---|---|
| 0 | initial state | — | 1000 | 300 | |
| 1 | : BEGIN | `` | 1000 | 300 | |
| 2 | : read(A) | 1000 | 300 | reads 1000 | |
| 3 | : A := A − 500 | `` (log flushed first) | 1000 (buffer updated, disk not yet) | 300 | WAL rule: log before data |
| 4 | : read(A) | 1000 | 300 | Isolation (locking) blocks until 's exclusive lock on A releases — never sees the intermediate value 500 | |
| 5 | : read(B) | 300 | reads 300 | ||
| 6 | : B := B + 500 | `` | 300 (buffer) | ||
| 7 | CRASH | log has ``, ``, `` — no commit record | 1000 | 300 | disk untouched; buffers lost |
| 8 | Restart: `RECOVER()` analysis phase | has no commit record → "active", not "committed" | |||
| 9 | `RECOVER()` undo phase | undo 's writes: restore A=1000 (no-op, disk already 1000), restore B=300 (no-op) | 1000 | 300 | atomicity preserved: it's as if never ran |
Now replay the same scenario but let reach commit before the crash:
| Step | Action | Log | Alice | Bob |
|---|---|---|---|---|
| 6′ | : B := B + 500 | `` | 1000 | 300 |
| 7′ | : COMMIT | `` flushed before returning success | 1000 | 300 |
| 8′ | CRASH (data pages never flushed) | log has full history incl. commit | 1000 | 300 |
| 9′ | `RECOVER()` analysis | has commit record → "committed" | ||
| 10′ | `RECOVER()` redo phase | replay `` then `` | 500 | 800 |
The two traces show atomicity ("all 's writes or none") and durability ("committed effects survive a crash") as directly enforced by the log, and isolation (step 4) as enforced by locking preventing from ever observing the half-finished value .
Common pitfalls & traps
| Pitfall | Why it happens | Fix |
|---|---|---|
| Flushing data pages before their log records | Assuming "write order doesn't matter" for performance | Enforce the WAL rule strictly: log record must be durable before the corresponding data page |
| Confusing "consistency" with "isolation" | Both sound like "nothing goes wrong," but consistency is about constraints, isolation about concurrent interference | Consistency = single-transaction correctness w.r.t. invariants; isolation = correctness under concurrent execution |
| Treating weak isolation levels as if they were serializable | Read Committed / Repeatable Read allow anomalies (dirty reads, non-repeatable reads, phantoms) that programmers forget about | Explicitly choose isolation level per workload; use Serializable when correctness depends on it |
| Assuming durability means "written to disk," ignoring the OS/disk write-back cache | A disk can acknowledge a write before it is physically persisted, silently breaking durability | Use `fsync`/equivalent flush guarantees and disks/controllers configured for correct write-barrier semantics |
| Forgetting that commit is not "instant" | Believing an application-level "commit returned" implies the transaction survives a crash that happens a microsecond later | The log flush for the commit record must complete before success is reported — that instant is the durability boundary |
| Ignoring deadlock in lock-based isolation | Two-phase locking guarantees serializability, not deadlock-freedom | Add deadlock detection (wait-for graph) or a prevention scheme (timeouts, wait-die/wound-wait) |
Pioneers & history
Origins · 1 minJim Gray and how this was discovered
Jim Gray coined and formalized the ACID acronym together with Andreas Reuter, building on his earlier work at IBM System R and Tandem Computers on transaction processing systems. His 1981 paper "The Transaction Concept: Virtues and Limitations" and the later book Transaction Processing: Concepts and Techniques (with Reuter, 1993) established the vocabulary — atomicity, consistency, isolation, durability — that every database system since has used to describe correctness under concurrency and failure. Gray also pioneered practical recovery algorithms and later worked on scalable data-intensive computing before his disappearance at sea in 2007.
Where this matters
Every mainstream relational database — PostgreSQL, MySQL/InnoDB, Oracle, Microsoft SQL Server, SQLite — implements ACID transactions as its core correctness contract, using write-ahead logging (often the ARIES-style algorithm) for atomicity/durability and locking or multiversion concurrency control for isolation. Financial systems (core banking, payment processing, stock exchanges) depend directly on atomicity and durability to guarantee money is never created or destroyed and never lost after a confirmed transfer. Airline reservation systems and inventory management rely on isolation to prevent two customers from double-booking the same seat or the same last unit of stock. Modern "NewSQL" distributed databases (Google Spanner, CockroachDB, YugabyteDB) extend ACID guarantees across machines, using distributed logging and consensus protocols to preserve the same four properties at global scale, while many NoSQL systems deliberately relax one or more ACID guarantees (often isolation or the scope of atomicity) in exchange for higher availability and throughput — a tradeoff made explicit in the discussion of distributed consistency models and the CAP theorem. Understanding ACID precisely is also the prerequisite for understanding why Write-Ahead Logging and Serializability and Schedules are built the way they are: both are engineering answers to the guarantees defined here.
Ask the tutor: How does the ARIES recovery algorithm extend the simple redo/undo scheme shown here to handle checkpoints and partial rollbacks efficiently?
Is ACID the same thing as "the transaction concept"?
No. The transaction concept defines what a transaction is — a logical unit of work with a begin and an end (commit or abort) — while ACID specifies the guarantees the DBMS makes about how that unit behaves under concurrency and failure. You can define transactions without ACID (some systems intentionally offer weaker guarantees for performance), but the transaction concept is the prerequisite vocabulary ACID is built on: you cannot state "the transaction commits atomically" without first having a notion of what a transaction and a commit are.
Why is consistency often considered the "odd one out" among the four properties?
Atomicity, isolation, and durability are guarantees the DBMS can enforce mechanically through logging and concurrency control, independent of what the transaction's code actually does. Consistency, by contrast, depends partly on application-level correctness: the DBMS can only enforce the invariants it knows about (declared constraints like foreign keys), while invariants encoded only in the application's logic (e.g., "total money is conserved") are the programmer's responsibility. Consistency is therefore a joint guarantee, upheld correctly only if atomicity and isolation are also upheld and the transaction's own code is correct.
Does every database really provide full ACID guarantees by default?
No — many production systems default to weaker settings for performance. Relational databases commonly default to a weaker isolation level, such as Read Committed, rather than full Serializable isolation, because full isolation reduces concurrency. Many NoSQL and distributed systems relax durability (asynchronous replication) or isolation (eventual consistency) by design, trading strict ACID guarantees for higher availability and lower latency, a tradeoff often summarized by the CAP theorem in distributed systems.
What exactly does "durable" mean if a disk itself later fails?
Durability is defined relative to the failures the system is designed to tolerate — typically crashes and power loss, assuming the persistent storage medium itself survives. If the physical disk is destroyed (fire, mechanical failure) durability alone does not protect the data; that requires additional mechanisms such as replication across independent disks or machines, redundant log copies, or backups. In practice, "durable" in ACID means "survives a crash of the database process or machine," and stronger disaster-tolerance is layered on top via replication.
How are atomicity and durability related mechanically, even though they are conceptually different?
Both are implemented using the same write-ahead log. Atomicity requires the ability to undo a transaction's writes if it doesn't commit, and durability requires the ability to redo a transaction's writes if it did commit but the data pages weren't yet flushed before a crash. The log stores exactly the information needed for both operations — before-images for undo, after-images for redo — so a single recovery procedure (analyze, redo, undo) delivers both guarantees simultaneously, as detailed under Write-Ahead Logging.
Can isolation be achieved without locking?
Yes. Multiversion concurrency control (MVCC), used by PostgreSQL, Oracle, and others, keeps multiple versions of each data item and lets each transaction read a consistent snapshot as of its start time, so readers never block writers and writers never block readers. Timestamp-ordering protocols are another lock-free approach, ordering transactions by assigned timestamps and aborting transactions that violate the intended order. Both achieve the same formal goal as locking — a serializable (or snapshot-isolated) outcome — through different mechanisms, each with its own tradeoffs in abort rates and read/write contention.
Why does the article mention serializability if isolation is already defined here?
Isolation is the informal goal ("transactions shouldn't interfere"); serializability is its precise mathematical definition ("the concurrent schedule is equivalent to some serial order of the same transactions"). Stating isolation without serializability would leave "shouldn't interfere" dangerously vague and unverifiable. The formal theory of schedules, conflict-equivalence, and the proof that two-phase locking guarantees serializability is developed fully under Serializability and Schedules, which this article intentionally sets up but does not exhaust.