← Назад к вопросам
Внутри ли транзакции вызывается post_save
3.0 Senior🔥 141 комментариев
#Django#Базы данных (SQL)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Вызывается ли post_save внутри транзакции
Это важный вопрос при работе с Django сигналами. Ответ: ДА, post_save вызывается ВНУТРИ транзакции, пока она ещё не завершена.
Основное поведение
from django.db import transaction
from django.db.models.signals import post_save
from django.dispatch import receiver
from myapp.models import User
@receiver(post_save, sender=User)
def on_user_created(sender, instance, created, **kwargs):
print(f"post_save вызван для {instance.id}")
# Мы ЕЩЁ ВНУТРИ транзакции!
# Если здесь будет ошибка, вся транзакция откатится
# Использование
with transaction.atomic():
user = User.objects.create(name='John')
# post_save вызывается ЗДЕСЬ, всё ещё внутри atomic()
print(f"User создан: {user.id}")
# Только после выхода из with откатывается/фиксируется транзакция
Временная шкала:
1. transaction.atomic() начинается
2. User.objects.create() сохраняет в БД
3. post_save сигнал вызывается ← ВСЕ ЕЩЕ ВНУТРИ транзакции
4. Обработчик post_save выполняется
5. Выход из atomic() блока
6. Транзакция коммитится (COMMIT) или откатывается (ROLLBACK)
Опасность: откат транзакции
from django.db import transaction
from django.db.models.signals import post_save
from myapp.models import User, UserProfile
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
# Если здесь ошибка — ВЕСЬ User также откатится!
# Это опасно:
try:
with transaction.atomic():
user = User.objects.create(name='John')
# post_save создаёт UserProfile
# Если создание UserProfile вызывает ошибку, User НЕ СОЗДАН!
except Exception as e:
print(f"Ошибка: {e}")
# User не существует в БД, потому что откатилась вся транзакция
Проверка: post_save вызывается внутри или после?
from django.db import transaction
from django.db.models.signals import post_save
from myapp.models import User
import time
@receiver(post_save, sender=User)
def on_user_save(sender, instance, **kwargs):
# Проверяем, есть ли текущая транзакция
current_transaction = transaction.get_connection().in_atomic_block
print(f"post_save: in_atomic_block = {current_transaction}")
# Тест 1: с atomic()
print("=== С atomic() ===")
with transaction.atomic():
user = User.objects.create(name='John')
# Выведет: post_save: in_atomic_block = True
# Тест 2: без atomic()
print("\n=== Без atomic() ===")
user = User.objects.create(name='Jane')
# Выведет: post_save: in_atomic_block = False
Решение: transaction.on_commit()
Чтобы выполнить действие ПОСЛЕ завершения транзакции, используй transaction.on_commit():
from django.db import transaction
from django.db.models.signals import post_save
from myapp.models import User
from myapp.tasks import send_welcome_email
@receiver(post_save, sender=User)
def on_user_created(sender, instance, created, **kwargs):
if created:
# Отправить письмо ПОСЛЕ завершения транзакции
transaction.on_commit(
lambda: send_welcome_email.delay(instance.id)
)
Временная шкала с on_commit():
1. transaction.atomic() начинается
2. User.objects.create() сохраняет в БД
3. post_save сигнал вызывается
4. on_commit() регистрирует callback (но не выполняет!)
5. Выход из atomic() блока
6. Транзакция КОММИТИТСЯ
7. ТЕ ТОЛЬКО ТЕПЕРЬ send_welcome_email.delay() выполняется
Практический пример: опасный vs безопасный код
Опасный код (внутри транзакции):
from django.db import transaction
from django.db.models.signals import post_save
from myapp.models import User, Notification
@receiver(post_save, sender=User)
def notify_admins(sender, instance, created, **kwargs):
if created:
# ОПАСНО: если это вызывает ошибку, User откатится!
Notification.objects.create(
type='new_user',
message=f'New user: {instance.name}',
admin=instance.id % 10 # Может быть несуществующий ID!
)
# Это откатит User если админ не найден
try:
with transaction.atomic():
user = User.objects.create(name='John')
except:
# User не создан!
pass
Безопасный код (после транзакции):
from django.db import transaction
from django.db.models.signals import post_save
from myapp.models import User
@receiver(post_save, sender=User)
def notify_admins(sender, instance, created, **kwargs):
if created:
# БЕЗОПАСНО: выполнится ПОСЛЕ завершения транзакции
transaction.on_commit(
lambda: send_notification.delay(instance.id)
)
# User ВСЕГДА создан, даже если send_notification вызовет ошибку
with transaction.atomic():
user = User.objects.create(name='John')
Множественные on_commit() обработчики
from django.db import transaction
transaction.on_commit(lambda: print("Callback 1"))
transaction.on_commit(lambda: print("Callback 2"))
# Выполняют в порядке регистрации после COMMIT
# Вывод:
# Callback 1
# Callback 2
Использование с savepoints
from django.db import transaction
with transaction.atomic():
user = User.objects.create(name='John')
# Savepoint — вложенная транзакция
with transaction.atomic():
user.name = 'Jane'
user.save()
# post_save вызывается, но savepoint ещё не завершён
# Здесь savepoint завершился, но внешняя транзакция ещё открыта
# post_save вызывается на уровне САМОЙ ВНУТРЕННЕЙ транзакции
# Но in_atomic_block = True для обеих уровней
Проверка в реальном проекте
from django.db import transaction, connection
from django.db.models.signals import post_save
from myapp.models import User
@receiver(post_save, sender=User)
def debug_transaction(sender, instance, **kwargs):
# Получить текущий уровень транзакции
print(f"Transaction in_atomic_block: {transaction.get_connection().in_atomic_block}")
# Проверить, есть ли активная транзакция
if connection.in_atomic_block:
print("ВЫ ВНУТРИ ТРАНЗАКЦИИ!")
else:
print("Вы НЕ внутри транзакции")
Решения для разных сценариев
Сценарий 1: Отправить email ПОСЛЕ создания User
# ❌ Неправильно
@receiver(post_save, sender=User)
def send_email(sender, instance, created, **kwargs):
if created:
send_welcome_email(instance.email) # Может откатиться User!
# ✅ Правильно
@receiver(post_save, sender=User)
def send_email(sender, instance, created, **kwargs):
if created:
transaction.on_commit(
lambda: send_welcome_email(instance.email)
)
Сценарий 2: Логировать в другую БД
# ❌ Опасно
@receiver(post_save, sender=User)
def log_user(sender, instance, **kwargs):
other_db_log = LogEntry(
action='user_created',
user_id=instance.id
)
other_db_log.save(using='analytics') # Может откатиться User!
# ✅ Правильно
@receiver(post_save, sender=User)
def log_user(sender, instance, **kwargs):
def log_async():
other_db_log = LogEntry(
action='user_created',
user_id=instance.id
)
other_db_log.save(using='analytics')
transaction.on_commit(log_async)
Сценарий 3: Вызвать external API
# ❌ Опасно
@receiver(post_save, sender=User)
def sync_to_api(sender, instance, **kwargs):
requests.post('https://api.example.com/users', json={
'id': instance.id,
'name': instance.name
}) # Может откатиться User если API недоступен!
# ✅ Правильно (используй Celery)
@receiver(post_save, sender=User)
def sync_to_api(sender, instance, **kwargs):
transaction.on_commit(
lambda: sync_to_api_task.delay(instance.id)
)
Заключение
- post_save вызывается ВНУТРИ транзакции — это основной факт
- Если post_save вызывает ошибку, ВЕСЬ User также откатится
- Используй transaction.on_commit() для действий ПОСЛЕ завершения транзакции
- Для асинхронных задач (email, API) всегда используй on_commit() + Celery
- Помни: post_save — не место для side effects, которые не должны откатываться вместе с моделью