PRO

Как решить проблему N+1 в Django ORM (select_related, prefetch_related)?

Проблема N+1 возникает, когда сначала выполняется один запрос за списком объектов, а затем по одному дополнительному запросу для каждой связанной записи. Для её решения используют select_related() для связей ForeignKey и OneToOneField, а prefetch_related() — для ManyToManyField и обратных связей.
Подробный ответ

Что такое N+1

Проблема N+1 появляется, когда приложение получает список из N объектов одним запросом, а затем в цикле обращается к связанным данным. Тогда Django выполняет ещё N запросов: по одному для каждого объекта.

articles = Article.objects.all()

for article in articles:
    print(article.author.username)

Если у статьи есть связь с автором, такой код может выполнить один запрос за статьями и отдельный запрос за автором каждой статьи.

select_related()

select_related() обычно используют для ForeignKey и OneToOneField. Django получает основные и связанные объекты через SQL JOIN в одном запросе.

articles = Article.objects.select_related('author')

prefetch_related()

prefetch_related() подходит для ManyToManyField, обратных ForeignKey и других связей, где JOIN может раздувать результат. Django выполняет отдельные запросы для связанных объектов и сопоставляет их в Python.

articles = Article.objects.prefetch_related('tags', 'comments')

Как найти проблему

Количество и длительность SQL-запросов удобно проверять через Django Debug Toolbar, тесты с подсчётом запросов или логи базы данных. Оптимизацию стоит применять после измерения, а не добавлять все prefetch заранее.

Как ответить на собеседовании

N+1 — это один запрос за списком объектов и N запросов за связанными данными. Для связей один-к-одному и многие-к-одному я использую select_related(), а для многие-ко-многим и обратных связей — prefetch_related().

Оцени свой прогресс

Честно оцени своё понимание этого вопроса, чтобы мы могли построить твой учебный трек максимально эффективно.
Читать в блоге