All guides

LEARN \u00b7 DEBUGGING GUIDE

Database transaction rollback not working: how to debug it

You wrap two inserts in a transaction. One fails. You call rollback. But the first insert is still in the database. The rollback did nothing.

IntermediateDatabase/debugging

What this usually means

A transaction rollback fails silently when the transaction was never really active, or when the database or ORM auto-committed before the rollback call. Common scenarios: the ORM is configured to auto-commit every statement, the transaction object was not properly awaited or passed through the call chain, the database connection was released back to the pool mid-transaction, or a nested transaction created a savepoint that was released but not rolled back.

( 01 )Fast diagnosis

The first ten minutes \u2014 establish facts before touching code.

  • 1Check if the database connection has auto-commit enabled. Most SQL clients default to auto-commit. You must explicitly begin a transaction.
  • 2Verify the transaction object is the same one used for all queries in the transaction. In many ORMs, you must pass the transaction object to each query method.
  • 3Check if connection pooling is releasing the connection mid-transaction. A transaction is bound to a single connection.
  • 4Add logging before and after the BEGIN, each query, and the COMMIT/ROLLBACK. Check the database query log to see what actually executed.
  • 5Test with a minimal script: open a connection, BEGIN, INSERT, ROLLBACK, SELECT. If this works but your app does not, the issue is in the application layer.
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchORM transaction documentation — how to properly start, pass, and commit/rollback a transaction
  • searchDatabase connection pool configuration — max connections, idle timeout, connection release policy
  • searchDatabase query logs — enable `log_statement=all` (Postgres) or general query log (MySQL) to see BEGIN/COMMIT/ROLLBACK
  • searchApplication code — trace the transaction object through every async call, middleware, and error handler
  • searchDatabase driver settings — auto-commit default, isolation level, read/write split routing
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningAuto-commit is enabled on the database connection, so every statement commits immediately
  • warningThe transaction object is not passed to inner function calls — they use a different connection
  • warningThe ORM's connection pool returns the connection to the pool before the transaction completes
  • warningAn uncaught error skips the rollback code entirely (the catch block is not reached)
  • warningNested transactions: an inner transaction creates a savepoint, which commits on success but the outer transaction rolls back
  • warningThe database client library silently reconnects on error and the new connection has no active transaction
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildExplicitly pass the transaction object to every database call inside the transaction boundary
  • buildUse the ORM's managed transaction helper (e.g. `sequelize.transaction(callback)`, `prisma.$transaction`) instead of manual BEGIN/COMMIT
  • buildSet the connection pool to hold connections during active transactions — do not release until commit/rollback
  • buildAdd a transaction middleware or decorator that ensures every request gets its own transaction context
  • buildUse database-level constraints (foreign keys, CHECK) as a safety net — they enforce consistency even if application rollback fails
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedWrite an integration test that inserts data, throws mid-transaction, and asserts the data was not persisted.
  • verifiedEnable database query logging and verify the BEGIN, INSERT, and ROLLBACK statements appear in order.
  • verifiedTest with concurrent requests to ensure transactions are isolated per connection.
  • verifiedForce a database error mid-transaction and verify the rollback cleans up all prior statements.
  • verifiedCheck the database directly with a SELECT after the rollback — do not trust the ORM's in-memory state.
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningAssuming the ORM handles transactions automatically without reading the docs
  • warningNot awaiting the transaction begin/commit/rollback in async code
  • warningUsing a different database connection for queries inside the transaction
  • warningCatching errors but not re-throwing after rollback — the caller never knows the operation failed
  • warningTesting transactions only in unit tests with mocked database calls