Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Transaction

Pattern

A reusable solution you can apply to your work.

“A transaction is a unit of work that you want to treat as ‘a whole.’ It has to either happen in full or not at all.” — Martin Kleppmann, Designing Data-Intensive Applications

Understand This First

  • Atomic – transactions provide atomicity for groups of operations.
  • Database – transactions are implemented by the database engine.
  • State – transactions protect state from corruption during multi-step changes.

Context

When an application performs multiple related operations on a Database (creating an order and decrementing inventory, transferring money between accounts, updating a user profile across several tables), those operations need to succeed or fail as a unit. A transaction is the mechanism that provides this guarantee. This is an architectural pattern because transactions are the primary tool for maintaining Consistency and Atomic behavior in data systems.

Problem

How do you ensure that a group of related data operations either all succeed or all fail, even in the face of crashes, errors, and concurrent access?

Without transactions, a multi-step operation can leave data in an inconsistent state. An error during step three of a five-step process means steps one and two took effect but steps four and five didn’t. The system is now in a state that no user action produced and no developer anticipated. Debugging this kind of corruption is among the most difficult work in software.

Forces

  • Multi-step operations are common. Most real business logic involves changing more than one record.
  • Crashes and errors can happen at any point during execution.
  • Multiple users operating concurrently can interfere with each other’s in-progress work.
  • Transactions add overhead and can create contention, reducing throughput.
  • Transactions within a single database are well supported; transactions spanning multiple systems are hard.

Solution

Wrap related operations in a database transaction. The database guarantees four properties, known as ACID:

  • Atomicity: All operations in the transaction complete, or none of them do. If anything fails, all changes are rolled back.
  • Consistency: The transaction moves the database from one valid state to another. Constraints (foreign keys, uniqueness, check constraints) are enforced.
  • Isolation: Concurrent transactions behave as if they ran one at a time. One transaction doesn’t see another’s half-finished work.
  • Durability: Once a transaction commits, its changes survive crashes, power failures, and restarts.

In practice, using transactions looks like this: begin the transaction, perform your operations, and either commit (make all changes permanent) or roll back (undo all changes). Most database libraries and ORMs provide a simple way to do this:

begin transaction
  create order record
  decrement inventory
  charge payment
commit transaction

If the payment charge fails, the order record and inventory decrement are automatically rolled back. The database returns to the state it was in before the transaction began.

How It Plays Out

A ride-sharing app assigns a driver to a ride. The operation involves updating the ride status, the driver’s availability, and creating a notification record. Without a transaction, a crash after updating the ride status but before updating the driver means the driver appears available but is actually assigned to a ride. With a transaction, all three updates either commit together or none of them do.

AI agents frequently generate code that performs multiple database writes without transaction boundaries. The code works during development because crashes and concurrency are rare, but it fails under production conditions. When reviewing agent-generated code that touches a database, ask: “If this code crashed halfway through, what state would the data be in?” If the answer is “a mess,” wrap the operations in a transaction.

Warning

Transactions that hold locks for a long time, especially those that make HTTP calls inside a transaction, can cause other operations to wait or time out. Keep transactions short: do your computation outside the transaction, then execute the database operations quickly inside it.

Example Prompt

“The ride assignment involves three writes: update the ride status, mark the driver unavailable, and create a notification. Wrap all three in a single database transaction.”

Consequences

Transactions give you confidence that multi-step operations are safe. They eliminate a large category of data corruption bugs. They let you reason about correctness in terms of complete operations rather than individual statements. ACID guarantees mean you can trust that committed data is real and complete.

The costs are performance and complexity. Transactions require the database to maintain locks and logs, which reduces throughput under heavy load. Long or contended transactions can cause other operations to block. Transactions across multiple databases or services (distributed transactions) are notoriously difficult and often avoided in favor of alternative patterns like sagas or compensating actions. Using transactions correctly also requires understanding isolation levels. Most databases default to a level that permits some subtle anomalies unless you explicitly choose a stricter setting.

  • Uses / Depends on: Atomic — transactions provide atomicity for groups of operations.
  • Uses / Depends on: Database — transactions are implemented by the database engine.
  • Enables: Consistency — transactions are the primary mechanism for maintaining consistency.
  • Refines: CRUD — complex operations compose CRUD within transactions.
  • Uses / Depends on: State — transactions protect state from corruption during multi-step changes.
  • Enables: Idempotency — transactions can be used to implement idempotent operations by checking for prior execution.

Further Reading

  • Designing Data-Intensive Applications by Martin Kleppmann — Chapter 7 provides an excellent, accessible treatment of transactions and their guarantees.