Что такое проблема N+1 запросов в Django и как ее решать
Что проверяют
Понимаете ли вы, как Django ORM превращает обращение к атрибуту в SQL, и умеете ли находить лишние запросы раньше, чем их найдёт продакшен. Следом почти всегда просят объяснить разницу между select_related и prefetch_related и показать, как вы ловите N+1 в тестах.
Как отвечать
Суть: QuerySet ленивый, а связанные объекты по умолчанию не загружаются. Цикл for book in Book.objects.all(): book.author.name делает один запрос за книгами и ещё по одному за автором каждой книги. На ста книгах это 101 запрос вместо одного.
Инструменты решения:
select_related: подтягивает связь черезJOINв том же запросе. Работает дляForeignKeyиOneToOneFieldв прямую сторону, то есть от книги к автору;prefetch_related: делает отдельный запрос на связь (WHERE author_id IN (...)) и склеивает результат в Python. Нужен дляManyToManyFieldи обратных связей, от автора к его книгам;Prefetch: объект дляprefetch_relatedс собственным QuerySet иto_attr, когда связанные записи надо отфильтровать или отсортировать;annotate: если в цикле нужен только счётчик,Count('books')посчитает его в одном запросе вместоauthor.books.count()на каждой строке.
Обнаружение: Django Debug Toolbar в разработке, connection.queries при DEBUG=True, а в тестах assertNumQueries, который фиксирует число запросов и не даёт регрессии вернуться. В DRF типичный источник N+1 это вложенные сериализаторы, лечится в get_queryset вьюсета.
Где спотыкаются
Советуют кэш как решение. Кэш прячет симптом, а первый холодный запрос всё равно сделает сотню обращений к базе.
Вызывают .filter() на предзагруженной связи внутри цикла. Такой вызов игнорирует кэш prefetch_related и снова идёт в базу на каждой итерации, фильтровать нужно заранее через Prefetch.
Путают области применения и пытаются сделать select_related по обратному ForeignKey или по many-to-many. Django ответит ошибкой FieldError, а JOIN по связи «ко многим» размножил бы строки результата.