← Назад к вопросам
Как работают транзакции в Django?
2.2 Middle🔥 221 комментариев
#Django#Базы данных (SQL)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Транзакции в Django ORM
Транзакция — это атомарная последовательность операций с БД. Либо все выполняются успешно (COMMIT), либо все откатываются при ошибке (ROLLBACK). Это гарантирует целостность данных.
ACID свойства транзакций
- Atomicity — либо всё, либо ничего
- Consistency — всегда валидное состояние БД
- Isolation — одновременные транзакции не мешают друг другу
- Durability — коммиченные данные сохранены на диске
Базовый контроль транзакций
1. Автокоммит (по умолчанию)
from django.db import models
# По умолчанию Django автоматически коммитит после каждого запроса
user = User.objects.create(username='john', email='john@example.com')
# Автоматический commit в конце запроса (в представлении)
2. Context Manager: transaction.atomic()
from django.db import transaction
# Выполнить несколько операций атомарно
with transaction.atomic():
user = User.objects.create(username='john', email='john@example.com')
Profile.objects.create(user=user, bio='Developer')
# Если вторая операция кинет исключение —
# обе операции откатятся, user не будет создан
# При нормальном выходе из блока — commit
3. Декоратор @transaction.atomic()
from django.db import transaction
@transaction.atomic
def transfer_money(from_account, to_account, amount):
"""Транзакция гарантирует, что либо оба счета обновятся, либо ни один"""
from_account.balance -= amount
from_account.save() # update 1
to_account.balance += amount
to_account.save() # update 2
# Обе операции откатятся, если вторая save() кинет исключение
# Вызов
transfer_money(account_a, account_b, 100)
Уровни изоляции (Isolation Levels)
# READ COMMITTED (по умолчанию в PostgreSQL)
# Видишь коммиченные данные, но не видишь изменения других транзакций
with transaction.atomic():
user = User.objects.get(id=1)
# Другая транзакция изменила этого пользователя
user.refresh_from_db() # Прочитать заново из БД
print(user.email) # Новый email
# REPEATABLE READ (в PostgreSQL и MySQL)
# Транзакция видит снимок данных на момент начала
# Не видит изменения других транзакций
with transaction.atomic():
count1 = User.objects.count()
# Другая транзакция добавила пользователей
count2 = User.objects.count()
# count1 == count2 (видим снимок на начало транзакции)
# SERIALIZABLE (строгая изоляция)
# Транзакции выполняются последовательно, как будто в одном потоке
Проблемы при параллельных транзакциях
1. Dirty Read (грязное чтение)
# Транзакция 1 изменяет, но не коммитит
# Транзакция 2 видит некоммиченные изменения
# Если транзакция 1 откатится — данные невалидны
# Решение: использовать READ COMMITTED (по умолчанию)
2. Lost Update (потеря обновления)
# ПЛОХО: race condition
# Процесс 1
user = User.objects.get(id=1)
user.balance += 50
user.save() # балл: 150
# Одновременно Процесс 2
user = User.objects.get(id=1)
user.balance += 50
user.save() # балл: 150 (потеря одного +50!)
# ХОРОШО: использовать F() для атомарного обновления
from django.db.models import F
User.objects.filter(id=1).update(balance=F('balance') + 50)
# Обновление происходит в БД, избегая race condition
3. Phantom Read (фантомное чтение)
# Транзакция 1 читает пользователей с balance > 100
# Транзакция 2 добавляет нового пользователя с balance > 100
# Транзакция 1 видит нового пользователя (фантом)
SELECT FOR UPDATE (Блокировка строк)
from django.db import transaction
# Заблокировать строку, чтобы другие транзакции ждали
with transaction.atomic():
user = User.objects.select_for_update().get(id=1)
# Пока эта транзакция открыта, никто не может
# обновить или заблокировать этого пользователя
user.balance -= 50
user.save()
# Блокировка снимается при COMMIT
# NOWAIT — не ждать, если строка заблокирована
with transaction.atomic():
try:
user = User.objects.select_for_update(nowait=True).get(id=1)
except transaction.TransactionManagementError:
print("Строка заблокирована другой транзакцией")
# SKIP LOCKED — пропустить заблокированные строки
users = User.objects.select_for_update(skip_locked=None).filter(status='pending')[:10]
# Возвращает только разблокированные строки (используется в очередях)
Пример: Банковский трансфер (атомарный)
from django.db import transaction
from django.db.models import F
@transaction.atomic
def transfer_money(from_user_id, to_user_id, amount):
"""Атомарный трансфер денег между счетами"""
# Заблокировать обе строки (избегаем deadlock если заблокировать в одинаковом порядке)
from_user = User.objects.select_for_update()\
.filter(id=from_user_id).get()
to_user = User.objects.select_for_update()\
.filter(id=to_user_id).get()
# Проверить достаточно ли средств
if from_user.balance < amount:
raise ValueError("Insufficient funds")
# Обновить оба счета атомарно
User.objects.filter(id=from_user_id)\
.update(balance=F('balance') - amount)
User.objects.filter(id=to_user_id)\
.update(balance=F('balance') + amount)
# Создать запись в истории
Transaction.objects.create(
from_user_id=from_user_id,
to_user_id=to_user_id,
amount=amount,
status='completed'
)
# Если вот тут ошибка — всё откатится
send_notification(to_user, f"Received {amount}")
Savepoints (точки сохранения)
from django.db import transaction
with transaction.atomic():
user = User.objects.create(username='john')
# Создать точку сохранения
with transaction.atomic():
try:
profile = Profile.objects.create(user=user, invalid_data='x')
except:
# Откатить только profile, user остаётся
transaction.set_rollback(True)
# user всё ещё существует, profile создан не был
Явное управление коммитом
from django.db import connection, transaction
# Явный контроль
transaction.set_autocommit(False)
try:
user = User.objects.create(username='john')
transaction.commit() # Явный commit
except Exception:
transaction.rollback() # Явный rollback
finally:
transaction.set_autocommit(True)
# Использовать raw SQL в транзакции
with transaction.atomic():
with connection.cursor() as cursor:
cursor.execute(
"UPDATE users SET balance = balance + %s WHERE id = %s",
[100, 1]
)
Конфигурация DATABASES
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydb',
'ATOMIC_REQUESTS': True, # Каждый request в отдельной транзакции
'CONN_MAX_AGE': 600, # Переиспользовать соединение 10 минут
}
}
Best Practices
- Используй transaction.atomic() для критичных операций
- SELECT FOR UPDATE для избегания race conditions
- F() выражения для атомарных обновлений
- Минимизируй время в транзакции (не делай HTTP запросы)
- Обрабатывай исключения при откатах
- Тестируй параллельные сценарии с threading/multiprocessing
Антипаттерны
# ❌ ПЛОХО: HTTP запрос внутри транзакции
@transaction.atomic
def create_user(data):
user = User.objects.create(**data)
response = requests.post('https://api.example.com/sync', json=user.as_dict()) # долго!
return user
# ✅ ХОРОШО: сначала создать, потом синхронизировать
@transaction.atomic
def create_user(data):
return User.objects.create(**data)
def sync_user_async(user_id):
user = User.objects.get(id=user_id)
requests.post('https://api.example.com/sync', json=user.as_dict())
Транзакции — это основа надёжного работы приложений. Понимание их поведения критично при разработке на Django.