⚖️ Старый vs Новый — Урок 36
⚡ Ключевые изменения
- Физическое удаление → мягкое удаление через
is_deleted+ менеджер - N+1 запросов в цикле →
select_related/prefetch_related - Ручной
set_autocommit(False)→with transaction.atomic() - Код после операции →
transaction.on_commit()для гарантий
1. Удаление записей: физическое vs мягкое
Из лекции (старое) — физическое удаление
# models.py
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
# ...
# views.py — DELETE выполняет физическое удаление
@api_view(['DELETE'])
def delete_book(request, pk):
book = Book.objects.get(pk=pk)
book.delete() # SQL: DELETE FROM books WHERE id=pk
return Response(status=204)
Запись удалена навсегда — восстановление невозможно без бекапа.
Современный подход — мягкое удаление
# managers.py
class SoftDeleteManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)
# models.py
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
is_deleted = models.BooleanField(default=False)
objects = SoftDeleteManager()
def delete(self, *args, **kwargs):
self.is_deleted = True
self.save()
# SQL: UPDATE books SET is_deleted=True WHERE id=pk
Запись остаётся в базе — можно восстановить, использовать для аудита.
2. Загрузка связанных объектов: N+1 vs оптимизация
Из лекции (старое) — N+1 запросов
# views.py
@api_view(['GET'])
def list_books(request):
books = Book.objects.all() # 1 запрос
result = []
for book in books:
result.append({
'title': book.title,
'publisher': book.publisher.name, # +1 запрос!
'genres': [g.name for g in book.genres.all()] # +N запросов!
})
return Response(result)
# Для 100 книг = 1 + 100 + 100 = 201 запрос
Критически медленно на продакшне с реальными объёмами данных.
Современный подход — оптимизация
# views.py
@api_view(['GET'])
def list_books(request):
# 3 запроса независимо от числа книг
books = (Book.objects
.select_related('publisher')
.prefetch_related('genres'))
result = []
for book in books:
result.append({
'title': book.title,
'publisher': book.publisher.name, # из JOIN-кеша
'genres': [g.name for g in book.genres.all()] # из prefetch-кеша
})
return Response(result)
# Для 100 книг = 3 запроса
3. Транзакции: ручной режим vs transaction.atomic
Из лекции (старое) — ручной autocommit
# views.py
@api_view(['POST'])
def create_book_and_publisher(request):
try:
transaction.set_autocommit(False) # опасно: легко забыть
publisher = Publisher.objects.create(...)
book = Book.objects.create(publisher=publisher, ...)
transaction.commit()
return Response(BookSerializer(book).data)
except Exception as e:
transaction.rollback()
return Response({'error': str(e)}, status=400)
finally:
transaction.set_autocommit(True) # не забыть вернуть!
Сложно, легко забыть finally → утечка соединения.
Современный подход — transaction.atomic
# views.py
@api_view(['POST'])
def create_book_and_publisher(request):
try:
with transaction.atomic():
publisher = Publisher.objects.create(...)
book = Book.objects.create(publisher=publisher, ...)
# При исключении — автоматический ROLLBACK
return Response(BookSerializer(book).data)
except Exception as e:
return Response({'error': str(e)}, status=400)
Чисто, безопасно, откат происходит автоматически при любом исключении.
4. Код после операции: после сохранения vs on_commit
Из лекции (старое) — код сразу после save
# views.py
@api_view(['POST'])
def create_book(request):
with transaction.atomic():
book = Book.objects.create(...)
# Уведомление отправляется немедленно
# Но транзакция могла ещё не записаться!
send_email_notification(book)
return Response(...)
Если сервер перезапустился между save и email — email отправлен, книга не создана. Рассинхрон.
Современный подход — transaction.on_commit
# views.py
@api_view(['POST'])
def create_book(request):
with transaction.atomic():
book = Book.objects.create(...)
# Гарантия: email только после успешного COMMIT
transaction.on_commit(
lambda: send_email_notification(book.id)
)
return Response(...)
Если транзакция откатится — callback не выполнится. Данные всегда согласованы.