Database transaction

A group of database writes treated as one unit: they all commit together, or they all get undone together.

either both updates happen or neithermoney left one account but never arrivedall or nothing database writestransactoinbegin commit rollbackundo the whole thing if one step failsatomic writehow do I stop half-finished saves

See it

Live demo coming soon

What it is

A transaction wraps several database statements in one deal: BEGIN, do the work, then COMMIT if everything went fine or ROLLBACK if anything blew up. Two different letters of ACID are doing that work, and people blur them constantly. Atomicity is the all-or-nothing part: every write lands or none does, which is why bank transfers are the canonical example, since debit and credit must go together. Isolation is the part that stops everyone else seeing your half-finished middle while the transaction is still open.

Reach for one whenever a single user action touches more than one row or table: create an order plus its line items plus decrement stock, or insert a user plus their default workspace. Keep the transaction short. Open it, write, close it. Every row you touch is locked until you commit, so a transaction that sits waiting on a slow API call is a traffic jam with your name on it.

Two traps. First, a transaction only protects the database: it cannot roll back the email you already sent or the Stripe charge you already made. Moving those calls after the commit fixes the rollback problem and creates a new one, because the process can die in the gap and the effect never happens at all. Firing after commit is fine when losing the effect is cheap or recoverable; when it is not, commit an outbox row in the same transaction and let a dispatcher send it with retries. Second, wrapping your writes does not automatically make concurrent updates safe. Two transactions can both read a stale value and both write, and you still lose one. That needs row locks (SELECT ... FOR UPDATE) or an isolation level above the default read committed.

Ask AI for it

Wrap this multi-step database operation in a single transaction. Use the ORM's transaction helper (Prisma $transaction, Drizzle db.transaction, SQLAlchemy session.begin) so every write inside either commits together or rolls back together on any thrown error. Do not call external services inside the transaction and do not fire them straight after the commit either: instead write an outbox row (effect type, payload, status) in the same transaction as the data, and have a separate dispatcher read unsent outbox rows and perform the email, webhook, or API call with retries and an idempotency key, marking the row sent only on success. For a synchronous payment, use an explicit state machine on the order (pending, authorized, captured, failed) so a crash mid-flow is resumable instead of ambiguous. Where two requests could update the same row at once, lock it with SELECT ... FOR UPDATE inside the transaction. Add a test that forces a failure on the last step and asserts no partial rows remain.

You might have meant

idempotencyrace conditionormjob queue