Ответ
Проблема N+1 запросов — это распространенный анти-паттерн производительности при работе с ORM, когда для извлечения N связанных сущностей выполняется 1 начальный запрос и N дополнительных запросов внутри цикла.
Пример проблемы:
Представим, что мы хотим вывести список постов и имена их авторов.
# 1. Выполняется ОДИН запрос для получения всех постов
posts = Post.objects.all()
# 2. В цикле для каждого из N постов выполняется
# еще один запрос для получения автора (всего N запросов)
for post in posts:
# На этой строке ORM делает ленивый (lazy) запрос к БД
print(f"Пост: {post.title}, Автор: {post.author.name}")
В итоге для N постов выполняется 1 + N запросов, что крайне неэффективно.
Основное решение — "жадная" загрузка (Eager Loading)
Это подход, при котором связанные данные загружаются заранее одним или несколькими оптимизированными запросами.
Реализация в популярных ORM:
Django ORM
select_related('author'): ИспользуетSQL JOINдля загрузки связанных объектов поForeignKeyилиOneToOneFieldодним запросом.prefetch_related('comments'): Выполняет отдельный запрос сWHERE ... IN (...)для загрузки связанных объектов поManyToManyFieldили обратнымForeignKey.
# Решение: всего 1 запрос с JOIN
posts = Post.objects.select_related('author').all()
for post in posts:
print(f"Пост: {post.title}, Автор: {post.author.name}") # Нет доп. запросов
SQLAlchemy
joinedload(User.posts): Аналогselect_related, используетLEFT OUTER JOIN.subqueryload(User.posts): Аналогprefetch_related, использует второйSELECTсIN.
from sqlalchemy.orm import sessionmaker, joinedload
# Решение: 1 запрос с JOIN
posts = session.query(Post).options(joinedload(Post.author)).all()
for post in posts:
print(f"Пост: {post.title}, Автор: {post.author.name}") # Нет доп. запросов