Ответ
Проблема N+1 запросов возникает, когда ORM выполняет один запрос для получения основной коллекции объектов (1), а затем по одному дополнительному запросу для загрузки связанных данных для каждого из N объектов. Это приводит к значительному падению производительности.
В SQLAlchemy эта проблема решается с помощью стратегий немедленной загрузки (eager loading), которые указываются через опцию .options().
Основные стратегии:
-
selectinload()(Рекомендуемый способ) Загружает связанные коллекции отдельным запросом, используяWHERE ... IN (...)для ID родительских объектов. Это самый эффективный способ для отношений "один-ко-многим".from sqlalchemy.orm import selectinload # Загрузит всех пользователей и одним доп. запросом все их адреса users = session.query(User).options(selectinload(User.addresses)).all() -
joinedload()ИспользуетLEFT OUTER JOINдля загрузки связанных данных в том же запросе. Идеально подходит для отношений "многие-к-одному" или "один-к-одному". Может вызывать избыточность данных для коллекций.from sqlalchemy.orm import joinedload # Загрузит все комментарии и сразу присоединит их авторов (пользователей) comments = session.query(Comment).options(joinedload(Comment.user)).all() -
subqueryload()Работает аналогичноselectinload, но использует подзапрос. В современных версиях SQLAlchemyselectinloadобычно предпочтительнее.
Глобальная настройка
Стратегию загрузки можно задать по умолчанию в определении модели с помощью параметра lazy в relationship:
from sqlalchemy.orm import relationship
class User(Base):
# ...
# Всегда использовать `selectinload` при доступе к addresses
addresses = relationship("Address", back_populates="user", lazy="selectin")