Database Transaction Management in Django: atomic(), ATOMIC_REQUESTS, Savepoints, and Autocommit Behavior

This article explains how Django manages database transactions, including its default autocommit behavior, per-request transactions using ATOMIC_REQUESTS, explicit transaction control with atomic(), nested savepoints, durability guarantees, error handling patterns, and performance considerations. It also clarifies why Django uses autocommit by default and highlights common pitfalls such as catching exceptions inside atomic blocks or inconsistent model state after rollbacks.

transaction, atomic, ATOMIC_REQUESTS, savepoint, autocommitrollback, commitDatabaseError, IntegrityError

~9 min read • Updated Mar 10, 2026

1. Introduction

Django provides powerful tools for managing database transactions. Transactions ensure that a group of database operations either all succeed or all fail, preserving data integrity.

2. Django’s Default Behavior: Autocommit Mode

By default, Django runs in autocommit mode. This means each SQL query is committed immediately unless it is executed inside an active transaction.

Django automatically uses transactions or savepoints for ORM operations that require multiple queries, such as update() and delete(). Django’s TestCase also wraps each test in a transaction for performance.

3. Per-Request Transactions with ATOMIC_REQUESTS

If you want each HTTP request to run inside a transaction, enable:


ATOMIC_REQUESTS = True

Behavior:

  • Before calling the view → Django starts a transaction.
  • If the view returns normally → commit.
  • If the view raises an exception → rollback.

Important notes:

  • This approach is simple but may reduce performance under heavy load.
  • StreamingHttpResponse runs outside the transaction.
  • Middleware and template rendering also run outside the transaction.

Opting out for specific views:


@transaction.non_atomic_requests
def my_view(request):
    ...

4. Explicit Transaction Control with atomic()

atomic() is Django’s primary API for managing transactions.

Features:

  • Commits changes if the block exits normally.
  • Rolls back changes if an exception occurs.
  • Can be nested (inner blocks use savepoints).
  • Can enforce durability with durable=True.

Decorator usage:


@transaction.atomic
def viewfunc(request):
    do_stuff()

Context manager usage:


with transaction.atomic():
    do_more_stuff()

5. Proper Error Handling with atomic()

The correct pattern is to wrap atomic() inside a try/except, not the other way around.

Correct example:


try:
    with transaction.atomic():
        generate_relationships()
except IntegrityError:
    handle_exception()

Why this matters:

If you catch exceptions inside an atomic block, Django may not detect that the transaction is broken, leading to TransactionManagementError when further queries are attempted.

6. Rollbacks and Model State

When a rollback occurs, Django does not revert the in-memory state of model instances. This can cause inconsistencies.

Example:


obj.active = True
try:
    with transaction.atomic():
        obj.save()
except DatabaseError:
    obj.active = False

For side effects like caching, use transaction.on_commit() to ensure updates happen only after a successful commit.

7. Savepoints and Nested atomic Blocks

Nested atomic blocks behave as follows:

  • Entering an inner block → create savepoint.
  • Exiting normally → release savepoint.
  • Exiting with exception → rollback to savepoint.

You can disable savepoints:


with transaction.atomic(savepoint=False):
    ...

This reduces overhead but breaks fine-grained error handling.

8. Durability with durable=True

Setting durable=True ensures the block must be the outermost atomic block and commits changes when it exits successfully.


with transaction.atomic(durable=True):
    ...

If nested, Django raises RuntimeError.

9. Autocommit and Why Django Uses It

In standard SQL, each query starts a transaction that must be explicitly committed or rolled back. This is inconvenient for application developers.

Autocommit simplifies this by wrapping each query in its own transaction:

  • Query succeeds → commit.
  • Query fails → rollback.

Although PEP 249 requires autocommit to be off by default, Django turns it on to improve developer experience.

Conclusion

Django offers flexible and powerful transaction management tools. atomic() provides precise control, while ATOMIC_REQUESTS enables per-request transactions. Understanding autocommit, savepoints, durability, and proper error handling is essential for building robust, high-performance applications.

1. Deactivating Django’s Transaction Management

You can completely disable Django’s transaction management for a specific database by setting:


AUTOCOMMIT = False

When autocommit is disabled:

  • Django will not automatically commit any queries.
  • You must manually commit every transaction, including those started by Django or third‑party libraries.
  • This is only recommended when implementing custom transaction middleware or unusual database workflows.

2. Performing Actions After a Successful Commit

Sometimes you need to run code only if the current transaction commits successfully—for example:

  • sending an email
  • triggering a background task
  • invalidating a cache

Django provides transaction.on_commit() for this purpose:


from django.db import transaction

def send_welcome_email():
    ...

transaction.on_commit(send_welcome_email)

Callbacks receive no arguments, but you can bind them using functools.partial():


from functools import partial

for user in users:
    transaction.on_commit(partial(send_invite_email, user=user))

Callback behavior:

  • Executed only after a successful commit.
  • Discarded if the transaction rolls back.
  • Executed immediately if no transaction is active.
  • Use robust=True to continue executing remaining callbacks even if one fails.

3. Savepoints and on_commit()

Savepoints (created by nested atomic() blocks) interact correctly with on_commit():

Case 1: No rollback → all callbacks run


with transaction.atomic():
    transaction.on_commit(foo)

    with transaction.atomic():
        transaction.on_commit(bar)

# foo() then bar() will run after the outer commit

Case 2: Inner savepoint rolled back → inner callbacks discarded


with transaction.atomic():
    transaction.on_commit(foo)

    try:
        with transaction.atomic():
            transaction.on_commit(bar)
            raise SomeError()
    except SomeError:
        pass

# Only foo() runs

4. Execution Order and Error Handling

Callbacks run in the order they were registered.

If a callback registered with robust=False raises an exception:

  • remaining callbacks will not run
  • the transaction is not rolled back (callbacks run after commit)

5. Timing of Callback Execution

Callbacks run only after autocommit is restored on the connection. This prevents callbacks from accidentally opening new implicit transactions.

When autocommit is active and you are outside any atomic() block, callbacks run immediately.

Important: Calling on_commit() when autocommit is disabled and no atomic block is active raises an error.

6. Using on_commit() in Tests

TestCase wraps each test in a transaction and rolls it back afterward, meaning:

  • transactions never commit
  • on_commit() callbacks never run

Solutions:

  • TestCase.captureOnCommitCallbacks() — captures callbacks for assertions
  • TransactionTestCase — commits transactions, but is slower due to database flushes

7. Why Django Has No Rollback Hook

A rollback hook is unreliable because many events can trigger implicit rollbacks, such as:

  • database connection drops
  • process termination

Instead of doing work and undoing it on rollback, Django encourages delaying the work until commit using on_commit().

8. Low-Level Transaction APIs

These APIs should be used only when implementing custom transaction logic. Prefer atomic() whenever possible.

Autocommit control:


get_autocommit(using=None)
set_autocommit(autocommit, using=None)

Notes:

  • Autocommit is on by default.
  • Turning it off gives you raw DB-API behavior.
  • You must manually restore autocommit.
  • You must ensure no transaction is active before re-enabling autocommit.
  • Django refuses to disable autocommit inside an atomic() block.

Manual commit and rollback:


commit(using=None)
rollback(using=None)

Django refuses to commit or roll back inside an active atomic() block to preserve atomicity.

9. What Is a Transaction?

A transaction is an atomic group of database operations. Even if the program crashes, the database guarantees:

  • either all changes are applied
  • or none of them are

To start a manual transaction, disable autocommit using set_autocommit(False).

Conclusion

Django provides a rich set of tools for advanced transaction control. on_commit() enables safe post‑commit actions, savepoints ensure nested atomicity, and low‑level APIs allow full manual control when needed. Understanding these mechanisms is essential for building robust, consistent, and high‑performance database workflows.

1. What Is a Savepoint?

A savepoint is a marker inside a transaction that allows you to roll back only part of the transaction instead of the entire thing. Savepoints are supported by:

  • SQLite
  • PostgreSQL
  • Oracle
  • MySQL (InnoDB engine)

Other backends expose the savepoint API but perform no actual savepoint operations.

2. Savepoints and Autocommit

Savepoints are not useful when autocommit is enabled (Django’s default). However, inside an atomic() block, Django groups operations into a pending transaction. A full rollback would undo everything, but savepoints allow fine‑grained rollbacks.

3. Savepoints in Nested atomic() Blocks

When atomic() blocks are nested, Django automatically creates savepoints. This allows inner blocks to roll back without affecting the outer block.

Although atomic() is recommended, Django still exposes low‑level savepoint functions.

4. Django’s Savepoint Functions

Create a savepoint:


sid = transaction.savepoint()

Commit a savepoint:


transaction.savepoint_commit(sid)

Roll back to a savepoint:


transaction.savepoint_rollback(sid)

Reset savepoint counter:


transaction.clean_savepoints()

5. Example Usage


@transaction.atomic
def viewfunc(request):
    a.save()

    sid = transaction.savepoint()

    b.save()

    if want_to_keep_b:
        transaction.savepoint_commit(sid)
    else:
        transaction.savepoint_rollback(sid)

6. Controlling Rollback Behavior

If an error occurs inside an atomic() block, Django rolls back the entire block—even if you handled the error with a savepoint.

To control this behavior:

Force rollback:


transaction.set_rollback(True)

Prevent rollback:


transaction.set_rollback(False)

Warning: Only set rollback=False after rolling back to a known‑good savepoint. Otherwise you risk breaking atomicity and corrupting data.

7. Database-Specific Notes

7.1 Savepoints in SQLite

SQLite supports savepoints, but the Python sqlite3 module has limitations:

  • Savepoints only work inside an atomic() block.
  • When autocommit is off, SQLite performs implicit commits before savepoint statements.
  • You cannot use atomic() when autocommit is disabled.

7.2 Transactions in MySQL

MySQL supports transactions only with certain table engines:

  • InnoDB → supports transactions
  • MyISAM → does not support transactions

If transactions are unsupported, Django always runs in autocommit mode.

7.3 Handling Exceptions in PostgreSQL

In PostgreSQL, once an exception occurs inside a transaction, all subsequent queries fail with:


current transaction is aborted, queries ignored until end of transaction block

To recover, you must roll back.

Option 1: Full rollback


a.save()
try:
    b.save()
except IntegrityError:
    transaction.rollback()
c.save()

This undoes all uncommitted changes, including a.save().

Option 2: Savepoint rollback


a.save()
sid = transaction.savepoint()
try:
    b.save()
    transaction.savepoint_commit(sid)
except IntegrityError:
    transaction.savepoint_rollback(sid)
c.save()

Here, a.save() is preserved even if b.save() fails.

Conclusion

Savepoints provide fine‑grained control over rollbacks inside transactions. Django manages them automatically in nested atomic() blocks, but also exposes low‑level APIs for advanced use cases. Understanding database‑specific behavior—especially in SQLite, MySQL, and PostgreSQL—is essential for using savepoints safely and effectively.

Written & researched by Dr. Shahin Siami