Skip to content

Grokem · https://www.grokem.com/databases/relational/acid-transactions-explained

RelationalExplainer

ACID and transactions in plain language

What atomicity, consistency, isolation and durability promise, shown with a money transfer that fails half-way and leaves nothing behind.

Grokem Editorial

Editorial desk

Published
  • Intermediate
  • 4 min read

What you will learn

  • Say what each of the four ACID properties guarantees
  • Use a transaction so a multi-step change succeeds or fails as one
  • Know what isolation levels trade off
  • Recognise what a transaction does not protect you from

Moving money between two accounts takes two steps: take it from one, add it to the other. If the power fails between the steps, the money has vanished. A transaction prevents that by making the steps one indivisible unit. The guarantees it gives are summarised as ACID.

The four letters

Atomicity: all or nothing. Either every statement in the transaction takes effect or none does. If one fails, the database undoes the rest.

Consistency: the rules hold after every commit. A transaction takes the database from one valid state to another. Constraints (NOT NULL, UNIQUE, CHECK, foreign keys) are the rules, and a change that would break one is refused.

Isolation: transactions do not see each other's half-finished work. Two people transferring money at the same moment do not interfere, as if they had taken turns. How strictly this is enforced is a setting, covered below.

Durability: committed means safe. Once the database says a commit succeeded, the change survives a crash. It is written to disk in a way that can be recovered.

Atomicity, demonstrated

An account may hold between 0 and 1,000. This script moves money, and the second transfer is built to fail after its first step has already run.

python
import sqlite3

conn = sqlite3.connect(":memory:")
conn.executescript(
    """
    CREATE TABLE accounts (
        id      INTEGER PRIMARY KEY,
        owner   TEXT,
        balance INTEGER CHECK (balance BETWEEN 0 AND 1000)
    );
    INSERT INTO accounts VALUES (1, 'Ana', 100), (2, 'Ben', 990);
    """
)


def transfer(source, target, amount):
    try:
        with conn:  # one transaction: commit on success, roll back on any error
            conn.execute("UPDATE accounts SET balance = balance - ? WHERE id = ?", (amount, source))
            print("   first update done, balances now", balances())
            conn.execute("UPDATE accounts SET balance = balance + ? WHERE id = ?", (amount, target))
        print(f"moved {amount} from {source} to {target}")
    except sqlite3.IntegrityError as error:
        print(f"refused ({error})")


def balances():
    return conn.execute("SELECT owner, balance FROM accounts ORDER BY id").fetchall()


print("start      ", balances())
transfer(1, 2, 5)
print("after ok   ", balances())
transfer(1, 2, 30)
print("after fail ", balances())

The output:

text
start       [('Ana', 100), ('Ben', 990)]
   first update done, balances now [('Ana', 95), ('Ben', 990)]
moved 5 from 1 to 2
after ok    [('Ana', 95), ('Ben', 995)]
   first update done, balances now [('Ana', 65), ('Ben', 995)]
refused (CHECK constraint failed: balance BETWEEN 0 AND 1000)
after fail  [('Ana', 95), ('Ben', 995)]

In the failed transfer the first update really did run (Ana briefly had 65), but Ben's balance would have passed 1,000, so the second update was refused. Because both were in one transaction, Ana's balance went back to 95. Without the transaction the money would have been lost. The CHECK constraint is consistency at work: the invalid state was never allowed to stand.

Isolation: the part with choices

Perfect isolation is expensive, so databases offer levels. They differ in which oddities you may see when other transactions run at the same time:

Oddity What happens
Dirty read You see another transaction's uncommitted change, which may then be rolled back
Non-repeatable read You read a row twice in one transaction and get different values because someone committed in between
Phantom read You run the same query twice and a different set of rows comes back

The standard levels, from loosest to strictest, are read uncommitted, read committed, repeatable read and serializable. Most systems default to read committed or repeatable read, and the exact guarantees differ by database, so read yours. The practical advice is simple: use the default, keep transactions short, and reach for a stricter level only when you can name the anomaly that is hurting you.

What a transaction does not do

  • It does not make slow things fast. An open transaction holds locks and can block others, so do the thinking before it begins.
  • It does not span systems. A transaction in your database cannot undo an e-mail already sent or a payment already taken from a card.
  • It does not replace constraints. Code that checks a rule before writing can be raced by a second request. A constraint in the database cannot.
  • It cannot save a bad design. If a fact lives in two places, a transaction can keep them in step, but it is better that it live in one.

Glossary

Transaction
A group of statements that the database treats as one unit of work.
Commit
Make a transaction's changes permanent and visible to others.
Rollback
Undo everything a transaction has done so far.
Constraint
A rule the database enforces itself, such as NOT NULL, UNIQUE or CHECK.
Isolation level
How much of other transactions' unfinished work a transaction is allowed to see.
Durability
A committed change survives a crash or power loss.

Frequently asked questions

Do all databases support transactions?

Relational databases do. Many other stores offer them only for a single record or in limited form, so check before assuming.

Why not make every operation a transaction?

Every statement already runs in one. The skill is grouping the statements that must succeed together and keeping the group short, because open transactions hold locks.

Next steps

  • Wrap your next multi-step write in a transaction
  • Read your database's documentation on isolation levels

References

From Grokem: https://www.grokem.com/databases/relational/acid-transactions-explained

How did this strike you?

Be the first to react – it is anonymous.

Comments (2)

  • Tom H.

    What is the default isolation level in PostgreSQL?

    • Grokem Editorial

      Read Committed: each statement sees the rows committed before it started. You can ask for a stricter level for a transaction when you need it, at the price of more retries when transactions conflict.

Keep going

All in topic

Choose what we may use. You can change this at any time with “Privacy choices” at the foot of every page. Details are in the Cookie policy and the Privacy policy.

Strictly necessary

Remembers your theme, the lessons you marked as done and this choice. It stays on your device, tracks nothing and cannot be switched off.

Always on
What exactly is collected, and why

Grokem’s own counts

Collects
The pages you open and how long you stay, how far you scroll, where you came from, your device type, browser, screen size, language and time zone. Your IP address is turned into a code that changes every day; it is never stored as it is.
Why
To see how long lessons are read and where readers leave. Not shared with anyone.
Stored
A random visitor ID, a visit ID and the time of your last page view in your browser’s storage (gr_vid, gr_sid, gr_last). No name or email is attached.