Короткий ответ
Проблема диагностируется по логам SQL-запросов (например, через django-debug-toolbar или connection.queries в тестах) — при сериализации списка объектов с related-полем выполняется по одному дополнительному запросу на каждый объект; решение — select_related/prefetch_related в queryset view.
Как это работает подробнее
Сериализатор с полем типа PrimaryKeyRelatedField/вложенным сериализатором для ForeignKey/ManyToMany при обращении к instance.related_field для каждого объекта списка вызывает отдельный SQL-запрос, если связанные данные не были заранее загружены вместе с основным queryset.
Пример кода
example.py
# Было: для списка из 50 статей выполняется 1 + 50 запросов (автор каждой статьи)
class ArticleViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# Стало: ровно 1-2 запроса независимо от количества статей
class ArticleViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Article.objects.select_related("author").prefetch_related("tags")
serializer_class = ArticleSerializer
# Проверка в тесте:
# with self.assertNumQueries(2):
# self.client.get("/api/articles/")Что использовать на практике
- Практика.
select_related('author')используют для ForeignKey/OneToOne (JOIN в одном запросе),prefetch_related('tags')— для ManyToMany/обратных ForeignKey (отдельный запрос с последующим Python-соединением в памяти). Это нужно применять именно в queryset, который передаётся вListAPIView/viewset (get_queryset), а не в самом сериализаторе.
Подводные камни
- Риск. Добавляют
select_related/prefetch_relatedв неправильном месте (например, в сериализаторе, который получает уже готовый queryset без возможности его переопределить) или забывают про вложенные уровни связей (prefetch_related('comments__author')); оценивают «решилось» только по уменьшению числа строк лога, не проверяя итоговое количество запросов числом (например,assertNumQueriesв тестах).