Ответ
Проблема N+1 запроса — это распространенная проблема производительности в ORM, когда для извлечения родительской сущности и связанных с ней дочерних сущностей выполняется один (1) запрос для родителя, а затем N дополнительных запросов (по одному для каждой дочерней сущности).
Это происходит из-за механизма "ленивой загрузки" (lazy loading), когда связанные данные подгружаются только в момент обращения к ним.
Пример проблемы (Django ORM)
# models.py
# class Author(models.Model): ...
# class Book(models.Model):
# author = models.ForeignKey(Author, ...)
# 1 запрос для получения всех книг
books = Book.objects.all()
# В цикле для каждой из N книг выполняется еще один запрос к БД
# для получения имени автора. Итого: 1 + N запросов.
for book in books:
print(f'"{book.title}" by {book.author.name}')
Решение: Жадная загрузка (Eager Loading)
Решение заключается в том, чтобы загрузить все необходимые данные одним или двумя оптимизированными запросами.
-
select_related: Используется для связейForeignKeyиOneToOne. Он "расширяет" основной запрос с помощьюJOIN, получая данные из связанных таблиц за один раз.# Выполняется 1 SQL-запрос с JOIN books = Book.objects.select_related('author').all() for book in books: # Данные об авторе уже загружены, дополнительный запрос не нужен print(f'"{book.title}" by {book.author.name}') -
prefetch_related: Используется для связейManyToManyFieldи обратныхForeignKey. Он выполняет отдельный запрос для связанных данных и "склеивает" их в Python. Всего выполняется два запроса, что всё равно эффективнее, чем N+1.# 1 запрос для авторов + 1 запрос для всех их книг authors = Author.objects.prefetch_related('book_set').all()
Игнорирование этой проблемы может привести к катастрофическому падению производительности приложения при работе с большими объемами данных.