A database transaction groups multiple operations so they either all succeed or all fail together (atomicity) — preventing partial, inconsistent updates. Django provides transaction.atomic to wrap operations in a transaction, ensuring data integrity for multi-step operations.
The problem transactions solve
# ❌ WITHOUT a transaction — partial failure leaves inconsistent data
def transfer_money(from_account, to_account, amount):
from_account.balance -= amount
from_account.save() # ✓ succeeds
# ...if the server crashes HERE, money vanished from one account...
to_account.balance += amount
to_account.save() # ✗ never runs → money LOST!
Without a transaction, if the second operation fails, the first has already committed — leaving the database in a broken, inconsistent state (money debited but not credited).
transaction.atomic — all-or-nothing
from django.db import transaction
# as a context manager
def transfer_money(from_account, to_account, amount):
with transaction.atomic(): # everything inside is ONE transaction
from_account.balance -= amount
from_account.save()
to_account.balance += amount
to_account.save()
# if ANY operation raises an exception, the WHOLE block is ROLLED BACK
# → either both saves happen, or neither does (atomicity)
# or as a decorator on a whole function/view
@transaction.atomic
def create_order(request):
order = Order.objects.create(...)
for item in items:
OrderItem.objects.create(order=order, ...)
# all-or-nothing: a failure anywhere rolls back the entire order creation
Inside atomic(), if any operation raises an exception, Django rolls back every change in the block — so the database is never left half-updated. On success, all changes commit together.
Nested atomic blocks (savepoints)
with transaction.atomic(): # outer transaction
do_something()
try:
with transaction.atomic(): # inner = a SAVEPOINT
risky_operation()
except Exception:
pass # inner rolls back to the savepoint; outer continues
Nested atomic blocks use savepoints — the inner block can roll back independently without aborting the whole outer transaction.
Important details
✓ Catch exceptions OUTSIDE the atomic block (catching inside can break rollback handling)
✓ select_for_update() locks rows within a transaction (prevent race conditions on updates)
with transaction.atomic():
account = Account.objects.select_for_update().get(pk=id) # row locked until commit
✓ ATOMIC_REQUESTS setting wraps EVERY request in a transaction (per-database)
✓ Keep transactions SHORT — long transactions hold locks, hurting concurrency
Why it matters
Database transactions are essential for data integrity whenever an operation involves multiple related database changes that must succeed or fail as a unit — and understanding transaction.atomic is important knowledge for building correct, reliable applications.
The core problem transactions solve is partial failure: without atomicity, if a multi-step operation fails midway (a crash, an exception, a constraint violation), the database is left in an inconsistent state — the classic money-transfer example (debited from one account but not credited to another) illustrates how this corrupts data and can have serious consequences. transaction.atomic provides the guarantee that operations are all-or-nothing (rolling back the entire block on any failure), which is necessary for any operation spanning multiple writes — creating an order with its line items, transferring funds, updating related records — to maintain a consistent database.
Understanding how to use atomic blocks (as a context manager or decorator), nested transactions with savepoints, and the important details — catching exceptions outside the block, using select_for_update() to lock rows and prevent race conditions in concurrent updates, and keeping transactions short to avoid holding locks — is practical knowledge for building robust applications.
Data consistency is a fundamental requirement of reliable systems, and transactions are the mechanism that ensures it for multi-step database operations, making this an important topic that distinguishes developers who write correct, integrity-preserving code from those whose applications can silently corrupt data under failure conditions or concurrent access — a frequently-relevant concern in any application handling important, related data changes.
