Chapter 13 · Transactions and Concurrency

ACID Properties and Transaction Boundaries

A transaction is not merely a group of SQL statements. It is the database boundary around one logical state transition: either every required change becomes visible, or none of it does.

Intermediate120–145 minutesACID reasoning + atomic transfer labLast reviewed: August 2026

Learning outcomes

Define the unit of correctness before writing SQL

01

Explain atomicity, consistency, isolation, and durability without treating them as vague marketing terms.

02

Distinguish database-enforced invariants from business rules enforced by an application workflow.

03

Choose transaction boundaries that contain one complete logical operation without holding resources longer than necessary.

04

Use SQLite autocommit, BEGIN, COMMIT, and ROLLBACK deliberately.

05

Implement and verify an atomic account transfer with a durable audit row.

A transaction is one state transition

Suppose the database state before a transfer is S₀. A successful transfer produces S₁; a failed transfer must leave the database equivalent to S₀. Intermediate states may exist inside the transaction, but other work must not treat them as committed truth.

\[ S_0 \xrightarrow{\;T\;} S_1 \qquad\text{or}\qquad S_0 \xrightarrow{\;T\;} S_0 \]
Read current state
Validate preconditions
Apply all writes
Verify invariants
COMMIT or ROLLBACK

The transaction boundary encloses the complete business transition, not each statement independently.

ACID as engineering properties

PropertyOperational meaningTypical mechanism
AtomicityAll effects of the transaction commit together, or the database removes them together.Transaction log, rollback journal, undo records, write-ahead logging.
ConsistencyA committed transaction preserves declared constraints and the application’s valid-state rules.CHECK, UNIQUE, FOREIGN KEY, triggers, and explicit pre/postcondition checks.
IsolationConcurrent transactions behave according to the selected visibility policy rather than exposing arbitrary partial work.Locks, snapshots, MVCC, validation, and serialization checks.
DurabilityAfter successful commit, the result survives later crashes according to the storage and configuration guarantees.Log flush, atomic commit protocol, recovery, replication, and reliable storage.
Consistency is not automatic business correctness

A database can enforce balance_cents >= 0, but it cannot infer whether a transfer was authorized, whether a daily limit was exceeded, or whether an external payment actually settled unless those rules are represented and checked.

Autocommit and explicit transactions

Most SQL clients operate in autocommit mode: each standalone statement is its own transaction. That is convenient for independent changes but dangerous when several statements form one operation.

sqlite · autocommit versus explicit transaction
-- Autocommit: these are two separate transactions.UPDATE accountSET balance_cents = balance_cents - 2500WHERE account_id = 1;UPDATE accountSET balance_cents = balance_cents + 2500WHERE account_id = 2;-- Explicit transaction: one atomic unit.BEGIN IMMEDIATE;UPDATE accountSET balance_cents = balance_cents - 2500WHERE account_id = 1  AND balance_cents >= 2500;UPDATE accountSET balance_cents = balance_cents + 2500WHERE account_id = 2;COMMIT;

If the process stops between the two autocommitted statements, money disappears from one account without reaching the other. The explicit transaction prevents that partial committed state.

Build the transfer laboratory

sqlite · chapter13_bank.sql
PRAGMA foreign_keys = ON;DROP TABLE IF EXISTS transfer_event;DROP TABLE IF EXISTS account;CREATE TABLE account (    account_id    INTEGER PRIMARY KEY,    owner_name    TEXT NOT NULL,    balance_cents INTEGER NOT NULL CHECK (balance_cents >= 0),    version_no    INTEGER NOT NULL DEFAULT 0 CHECK (version_no >= 0)) STRICT;CREATE TABLE transfer_event (    transfer_id   TEXT PRIMARY KEY,    from_account  INTEGER NOT NULL REFERENCES account(account_id),    to_account    INTEGER NOT NULL REFERENCES account(account_id),    amount_cents  INTEGER NOT NULL CHECK (amount_cents > 0),    created_at    TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,    CHECK (from_account <> to_account)) STRICT;INSERT INTO account (account_id, owner_name, balance_cents) VALUES    (1, 'Ava Chen',  12500),    (2, 'Liam Ortiz', 7500),    (3, 'Mina Park',  5000);

The schema encodes local invariants: balances cannot be negative, transfer amounts must be positive, accounts must exist, and a transfer cannot target the same account.

Execute an atomic transfer

sqlite · transfer_txn.sql
BEGIN IMMEDIATE;UPDATE accountSET balance_cents = balance_cents - 2500,    version_no = version_no + 1WHERE account_id = 1  AND balance_cents >= 2500;-- The application must verify that exactly one row changed.SELECT changes() AS debit_rows;UPDATE accountSET balance_cents = balance_cents + 2500,    version_no = version_no + 1WHERE account_id = 2;SELECT changes() AS credit_rows;INSERT INTO transfer_event    (transfer_id, from_account, to_account, amount_cents)VALUES    ('tr_20260805_001', 1, 2, 2500);SELECT SUM(balance_cents) AS total_balance_centsFROM account;COMMIT;

The total balance is an invariant:

\[ \sum_i b_i^{\text{before}} = \sum_i b_i^{\text{after}} \]

The application should verify both row counts and the invariant before committing. If a debit affects zero rows because funds are insufficient, it must roll back instead of continuing.

Choose the boundary deliberately

Narrow

Too small

Committing each statement separately exposes partial business operations.

Wide

Too large

Keeping a transaction open during user input, network calls, or report rendering increases contention and failure cost.

Right

Business-sized

Begin after external input is ready; perform database reads and writes; verify; commit; then publish side effects.

External

Not magically atomic

A database transaction cannot by itself roll back an email, HTTP request, or third-party payment. Use an outbox, saga, or compensating action.

Stable

Retry-aware

The transaction must be safe to repeat or protected by an idempotency key when the client cannot know whether commit succeeded.

Boundary anti-patterns

Anti-patternFailure modeBetter design
Call a remote API while holding locksLong waits, deadlocks, timeouts, and uncertain recovery.Call before the transaction when safe, or write an outbox record and process it after commit.
Read outside, write later without a guardThe decision is based on stale state.Recheck the predicate inside the transaction or use a version column.
Commit before audit insertionBusiness state changes without the required evidence row.Write state and audit event in the same transaction.
One transaction for an entire import fileHuge rollback cost and prolonged write ownership.Use validated batches and savepoints with resumable progress.
Assume COMMIT means external durability under every settingStorage configuration can weaken guarantees.Understand synchronous, WAL, replication, and infrastructure settings.

Checkpoint

Reason about boundaries

  1. Why are two individually correct UPDATE statements insufficient for a transfer?
  2. Which ACID property is primarily violated if another session observes the debit before the credit?
  3. Does a CHECK constraint guarantee that the transfer was authorized?
  4. Why should a transaction normally exclude a slow HTTP call?
  5. What should the application do if the guarded debit updates zero rows?
Review the answers

The two updates must commit as one atomic transition. Exposing the intermediate debit is an isolation problem and committing only the debit is an atomicity problem. CHECK validates represented data, not authorization. Slow external calls extend lock and snapshot lifetimes. A zero-row debit means the precondition failed, so the application should roll back and report the business conflict.

Summary and references

  • ACID describes observable transaction guarantees, not a replacement for explicit business rules.
  • Autocommit is suitable for independent statements; multi-statement operations need an explicit boundary.
  • Good boundaries are complete enough for correctness and short enough for concurrency.
  • Verify affected-row counts and invariants before commit.
  • External side effects require patterns such as outbox processing or compensating actions.

References

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.