DjangoSeniorCoding
Как управлять транзакциями базы данных в Django с помощью atomic()?
atomic() создаёт savepoint (вложенный вызов) или полную транзакцию (верхний уровень). При исключении всё откатывается; можно использовать как декоратор или контекстный менеджер. Ключевые нюансы — on_commit, select_for_update и вложенность.
Как работает transaction.atomic()
Django управляет соединением с БД в режиме autocommit по умолчанию — каждый запрос немедленно коммитится. transaction.atomic() открывает явную транзакцию (или savepoint при вложенном вызове). При выходе без исключения — коммит, при необработанном исключении — автоматический rollback.
from django.db import transaction
from .models import Account, TransferLog
def transfer_funds(from_id: int, to_id: int, amount: int) -> None:
with transaction.atomic():
# select_for_update блокирует строки до конца транзакции
accounts = (
Account.objects
.select_for_update()
.filter(id__in=[from_id, to_id])
.order_by('id') # фиксируем порядок блокировки — предотвращает дедлок
)
acc_map = {a.id: a for a in accounts}
source = acc_map[from_id]
target = acc_map[to_id]
if source.balance < amount:
raise ValueError('Insufficient funds')
source.balance -= amount
target.balance += amount
Account.objects.bulk_update([source, target], ['balance'])
TransferLog.objects.create(
from_account=source,
to_account=target,
amount=amount,
)
Декоратор vs контекстный менеджер
# Как декоратор
@transaction.atomic
def create_order(user, items):
order = Order.objects.create(user=user)
for item in items:
OrderItem.objects.create(order=order, **item)
return order
# Как контекстный менеджер с частичным rollback
def process_batch(records):
results = []
for record in records:
try:
with transaction.atomic(): # каждый — отдельный savepoint
obj = Record.objects.create(**record)
results.append({'id': obj.id, 'status': 'ok'})
except Exception as e:
results.append({'status': 'error', 'detail': str(e)})
return results
on_commit: запуск кода после коммита
Задачи Celery, отправку email и любые побочные эффекты нужно запускать только после успешного коммита:
from django.db import transaction
from .tasks import send_welcome_email
def register_user(email: str, password: str):
with transaction.atomic():
user = User.objects.create_user(email=email, password=password)
# НЕ: send_welcome_email.delay(user.id) — задача стартует до коммита!
transaction.on_commit(lambda: send_welcome_email.delay(user.id))
return user
Вложенные транзакции и savepoints
def outer():
with transaction.atomic(): # BEGIN
do_something()
try:
with transaction.atomic(): # SAVEPOINT sp1
risky_operation() # может бросить исключение
except SomeError:
pass # ROLLBACK TO SAVEPOINT sp1
do_more() # продолжаем
# COMMIT
Использование с PostgreSQL DEFERRABLE
from django.db import connection
def create_circular_refs(a_data, b_data):
with transaction.atomic():
# Откладываем проверку FK до конца транзакции
with connection.cursor() as cur:
cur.execute('SET CONSTRAINTS ALL DEFERRED')
a = ModelA.objects.create(**a_data)
b = ModelB.objects.create(ref_to_a=a, **b_data)
a.ref_to_b = b
a.save(update_fields=['ref_to_b'])
Подводные камни
select_for_update()внеatomic()бросаетTransactionManagementError— блокировка без транзакции бессмысленна и Django это запрещает.- Обработка исключения внутри
atomic()без вложенного savepoint переводит транзакцию в «broken» состояние — последующие запросы дадутdjango.db.utils.InternalError: current transaction is aborted. on_commitне вызывается в тестах, использующихTestCase, — только вTransactionTestCaseили с@pytest.mark.django_db(transaction=True).- Длинные транзакции с
select_for_update()блокируют строки и могут вызвать дедлок — всегда обращайтесь к строкам в фиксированном порядке (сортировка поid). - Autocommit Django не означает autocommit PostgreSQL — Django управляет транзакциями сам; включение
ATOMIC_REQUESTS = Trueоборачивает каждый HTTP-запрос в транзакцию. atomic(using='secondary')— для мультибазовых проектов; безusingпо умолчанию используетсяdefaultalias.- Вызов
atomic(savepoint=False)отключает savepoint для вложенного блока — ошибка внутри откатит всю внешнюю транзакцию.
Common mistakes
- Описывать transactions atomic только как термин и не показывать механизм на минимальном примере.
- Игнорировать ошибки, пустые данные, конкурентный доступ или границы транзакции.
- Не связывать поведение с официальным контрактом Django и реальной эксплуатацией.
What the interviewer is testing
- Объясняет transactions atomic через последовательность действий, а не через набор ключевых слов.
- Приводит короткий кодовый пример или production-сценарий с ожидаемым поведением.
- Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.
- Умеет обсудить отказ, наблюдаемость и rollback без изменения публичного контракта.