Как найти и исправить N+1 в Django
Вопрос
Страница списка заказов тормозит. Как вы найдёте и исправите проблему N+1?
Попробуйте сами
Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.
Показать ответ и разбор
Коротко
Что должно прозвучать в ответе
Симптом: число запросов растёт с числом объектов
Поиск: django-debug-toolbar, логи SQL, `assertNumQueries` в тестах, APM
`select_related` для FK и OneToOne (JOIN)
`prefetch_related` и `Prefetch` для ко многим
Аннотации вместо подсчётов в цикле, `only` и `values` для лишних полей
Закрепить тестом на число запросов
Что могут спросить следом
- Когда JOIN хуже, чем prefetch?
- Как N+1 проявляется в сериализаторах DRF?
Разбор
Образцовый ответ: «Признак N+1: число SQL-запросов на странице растёт вместе с числом объектов. Локально смотрю django-debug-toolbar, в тестах assertNumQueries, в проде трейсы APM, где видны сотни одинаковых запросов. Дальше смотрю, какие связи трогаются в цикле, шаблоне или сериализаторе. Для ForeignKey и OneToOne добавляю select_related: связанные объекты придут одним JOIN. Для «ко многим», то есть позиций заказа или тегов, prefetch_related: второй запрос с IN по всем id. Если для вложенных объектов тоже нужны связи или фильтр, использую Prefetch со своим QuerySet. Если в цикле считается количество или сумма, заменяю на annotate(Count(...)). Лишние поля отсекаю only или values. В конце закрепляю результат тестом с assertNumQueries, чтобы регрессия не вернулась. JOIN хуже prefetch, когда связь «ко многим» размножает строки основного запроса».
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.
Предыдущий вопрос
select_related против prefetch_related
Следующий вопрос
Гонки данных в Django и как их предотвращать
Ещё по теме «Django ORM и N+1»
- Задвоение сумм при нескольких annotate
- Посчитать SQL-запросы при ленивой загрузке
- Как prefetch_related склеивает данные
- exists() и count() вместо загрузки строк
- Атомарное увеличение счётчика
- Prefetch с вложенным select_related
- transaction.atomic
- Когда выполняется QuerySet