Короткий ответ
N+1 проявляется, когда для одного списка из N объектов сериализатор выполняет по отдельному запросу на связанные данные для каждого элемента; диагностируют по логам SQL-запросов (один запрос на список + N похожих запросов), а устраняют через предварительную загрузку связанных данных одним batched-запросом (eager loading / join / IN-запрос).
Как это работает подробнее
если эндпоинт возвращает список заказов с полем "имя клиента", а код наивно обращается к order.customer.name для каждого заказа в цикле, при ленивой загрузке ORM это превращается в отдельный SQL-запрос на каждого клиента — при 100 заказах это 1 + 100 запросов вместо одного-двух.
Пример кода
example.py
# Намеренная проблема N+1 (SQLAlchemy, lazy loading по умолчанию)
orders = session.query(Order).filter(Order.status == "paid").all()
result = [{"id": o.id, "customer_name": o.customer.name} for o in orders] # отдельный запрос на каждый o.customer
# Исправление: eager loading одним запросом
from sqlalchemy.orm import joinedload
orders = (
session.query(Order)
.options(joinedload(Order.customer))
.filter(Order.status == "paid")
.all()
)
result = [{"id": o.id, "customer_name": o.customer.name} for o in orders] # customer уже подгруженЧто использовать на практике
- Рекомендация. включить в логи или
EXPLAINколичество выполненных запросов на один вызов эндпоинта, сравнить его с ожидаемым (константным, не зависящим от размера списка); в SQLAlchemy использоватьjoinedload/selectinload, в Django ORM —select_related/prefetch_related, либо вручную собрать все нужные ID и одним запросом сINподтянуть связанные записи в словарь перед сериализацией.
Подводные камни
- Риски. обнаруживать проблему только в production под нагрузкой, а не на этапе код-ревью или тестов с логированием SQL; "решать" N+1 кэшированием отдельных запросов вместо устранения причины, что скрывает проблему лишь частично и не помогает при холодном кэше; использовать eager loading "на всякий случай" для всех связей, включая те, что не нужны в конкретном ответе, что увеличивает объём данных, перекачиваемых из БД, без необходимости.