Зачем нужен on_commit в Django?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
on_commit в Django: для чего нужен
on_commit — это встроенный в Django механизм для выполнения код после успешного завершения транзакции БД. Это критически важная функция для обеспечения консистентности данных и надежности асинхронных операций.
Основная проблема, которую решает on_commit
Представьте, что вы создаете пользователя и отправляете ему письмо на почту:
# ❌ ПЛОХО: может привести к ошибкам
from django.core.mail import send_mail
from .models import User
def create_user(email, password):
user = User.objects.create_user(email=email, password=password)
send_mail(
'Welcome!',
'Thanks for signing up',
'from@example.com',
[email]
)
return user
Почему это плохо:
- Если письмо отправляется, но транзакция откатится — пользователь не будет в БД, но письмо уже отправлено
- Отправка почты может быть медленной — блокирует ответ HTTP, плохо для UX
- Если отправка письма упадет — вся операция создания пользователя откатится
Решение: on_commit callback
from django.db import transaction
from django.core.mail import send_mail
from .models import User
def create_user(email, password):
def send_welcome_email():
send_mail(
'Welcome!',
'Thanks for signing up',
'from@example.com',
[email]
)
user = User.objects.create_user(email=email, password=password)
# Этот callback выполнится ТОЛЬКО если транзакция успешно завершится
transaction.on_commit(send_welcome_email)
return user
Теперь:
- Пользователь создается в БД
- Только если это успешно — выполняется callback
- Письмо отправляется ПОСЛЕ успешного коммита
- Никогда не отправим письмо без пользователя в БД
Как это работает
from django.db import transaction
def process_order(order_id):
order = Order.objects.get(id=order_id)
order.status = 'processing'
order.save()
# Регистрируем callback, который выполнится ПОСЛЕ save()
transaction.on_commit(lambda: send_order_notification(order_id))
return order
Порядок выполнения:
1. На_commit() вызывается (регистрирует callback)
2. order.save() выполняется (инсерт в БД)
3. Транзакция завершается (COMMIT)
4. ✅ Callback выполняется
Если произойдет ошибка на шаге 2 или 3 — callback никогда не выполнится.
Практические примеры
Пример 1: Отправка email после создания пользователя
from django.db import transaction
from django.core.mail import send_mail
from .models import User
def register_user(email, name):
user = User.objects.create_user(email=email, username=name)
transaction.on_commit(
lambda: send_mail(
'Welcome',
f'Hi {name}',
'noreply@example.com',
[email]
)
)
return user
Пример 2: Запуск задачи Celery после изменения статуса
from django.db import transaction
from .tasks import process_payment
from .models import Order
def approve_order(order_id):
order = Order.objects.get(id=order_id)
order.status = 'approved'
order.save()
# Запустить обработку только если заказ успешно сохранен
transaction.on_commit(
lambda: process_payment.delay(order_id)
)
Пример 3: Инвалидация кеша
from django.db import transaction
from django.core.cache import cache
from .models import Product
def update_product(product_id, new_price):
product = Product.objects.get(id=product_id)
product.price = new_price
product.save()
# Очистить кеш только после успешного сохранения
transaction.on_commit(
lambda: cache.delete(f'product:{product_id}')
)
Nested transactions и on_commit
Важно понимать, что on_commit срабатывает только на самом внешнем уровне транзакции:
from django.db import transaction
with transaction.atomic():
# Outer transaction
user = User.objects.create(name='Alice')
transaction.on_commit(lambda: print("Outer commit"))
with transaction.atomic():
# Nested transaction (savepoint)
user.email = 'alice@example.com'
user.save()
transaction.on_commit(lambda: print("Inner commit")) # не сработает!
# Вывод:
# Outer commit
# Inner commit (если это не savepoint)
Если это savepoint (не SERIALIZABLE), callback из вложенной транзакции не выполнится.
on_commit с несколькими callbacks
Можно регистрировать несколько callbacks:
from django.db import transaction
def create_user_with_profile(email, name):
user = User.objects.create_user(email=email, username=name)
Profile.objects.create(user=user, bio='New member')
# Несколько callbacks
transaction.on_commit(lambda: send_welcome_email(email))
transaction.on_commit(lambda: log_user_creation(user.id))
transaction.on_commit(lambda: update_analytics(user.id))
return user
Все будут выполнены в порядке регистрации после успешного коммита.
Антипаттерны: что НЕЛЬЗЯ делать
# ❌ ПЛОХО: запросы к БД в callback могут упасть
def bad_callback():
user = User.objects.get(id=user_id) # может быть ошибка
user.last_seen = now()
user.save()
transaction.on_commit(bad_callback)
# ✅ ХОРОШО: используй на_commit для побочных эффектов, не БД логики
def good_callback():
send_slack_notification(f"User {user_id} created")
log_event('user_created', user_id)
transaction.on_commit(good_callback)
Сравнение: с on_commit и без
| Сценарий | Без on_commit | С on_commit |
|---|---|---|
| Письмо отправляется, но save() откатится | Письмо отправлено, пользователя нет (BUG) | Письмо не отправлено (OK) |
| Блокирует HTTP response | Да, если письмо медленное | Нет, запускается после ответа |
| Консистентность данных | Нарушена | Гарантирована |
| Сложные операции | Может быть проблемно | Работает надежно |
Реальный сценарий: создание заказа с уведомлением
from django.db import transaction
from django.core.mail import send_mail
from .models import Order, OrderItem
from .tasks import send_slack_notification
@transaction.atomic # Весь метод в одной транзакции
def create_order(user, items_data):
order = Order.objects.create(
user=user,
status='pending',
total=sum(item['price'] * item['qty'] for item in items_data)
)
for item_data in items_data:
OrderItem.objects.create(
order=order,
product_id=item_data['product_id'],
quantity=item_data['qty']
)
# Callback выполнится ТОЛЬКО если весь блок атомик выполнился успешно
transaction.on_commit(
lambda: send_slack_notification.delay(f"New order {order.id}")
)
return order
Вывод
on_commit в Django — это essential pattern для:
- Обеспечения консистентности данных
- Запуска асинхронных задач (Celery)
- Отправки уведомлений (email, SMS, push)
- Инвалидации кеша
- Ведения логов аудита
Без него вы рискуете иметь расхождения между БД и внешними системами. Это одна из самых важных фич Django для production-grade приложений.