Гайды
Django ORM: оптимизация запросов, select_related и prefetch_related
N+1, JOIN и отдельные IN-запросы, Prefetch, only/defer, exists, iterator, annotate и индексы в Meta.
~12 мин чтения
Django ORM: оптимизация запросов, select_related и prefetch_related
Django ORM удобен, но легко получить N+1 запросов. Инструменты: select_related (JOIN для ForeignKey/OneToOne), prefetch_related (отдельный запрос + джойн в Python для reverse FK и M2M), only/defer, аннотации и профилирование. База проекта — Django: первый проект; сравнение с «сырым» SQL и планировщиком — EXPLAIN ANALYZE в PostgreSQL. Для асинхронного стека без Django ORM на стороне API часто смотрят на SQLAlchemy 2.0.
1. N+1: как выглядит
for order in Order.objects.all():
print(order.customer.name) # каждый раз SELECT к customer
Решение — заранее подтянуть связь.
2. select_related (SQL JOIN)
Для ForeignKey и OneToOne в сторону «один объект»:
orders = Order.objects.select_related("customer").all()
Один запрос с JOIN вместо 1+N.
3. prefetch_related (отдельный IN)
Для reverse ForeignKey, ManyToMany:
authors = Author.objects.prefetch_related("books").all()
Два запроса: авторы, затем книги с WHERE author_id IN (...).
Prefetch объект
from django.db.models import Prefetch
qs = Book.objects.filter(published=True)
authors = Author.objects.prefetch_related(
Prefetch("books", queryset=qs, to_attr="published_books")
)
to_attr — кэш в атрибуте списка без повторных запросов в шаблоне.
4. only / defer
Уменьшить объём строк:
Product.objects.only("id", "name")
Product.objects.defer("heavy_json_field")
Осторожно: при обращении к «выключенному» полю будет дополнительный запрос.
5. exists / count
if Order.objects.filter(customer_id=1).exists():
...
exists() быстрее, чем count() > 0. Для count() без фильтра на больших таблицах — дорого.
6. iterator() для больших выборок
for row in HugeModel.objects.iterator(chunk_size=2000):
process(row)
Не загружает весь queryset в память.
7. annotate и агрегации на БД
from django.db.models import Count
Author.objects.annotate(n_books=Count("books")).filter(n_books__gte=3)
Считать на Python в цикле хуже, если можно в SQL.
8. Отладка запросов
from django.db import connection
print(len(connection.queries))
# Временно в settings: LOGGING для django.db.backends
django-debug-toolbar в dev — визуализация SQL и дублирования.
9. Индексы в модели
class Meta:
indexes = [
models.Index(fields=["status", "-created_at"]),
]
Согласуйте с реальными фильтрами и планами на уровне PostgreSQL — см. EXPLAIN ANALYZE.
10. Массовые вставки и обновления
bulk_create / bulk_update снижают round-trip к БД; помните про лимиты параметров в PostgreSQL и разбивайте на чанки. update_or_create / get_or_create удобны, но под нагрузкой дают уникальные гонки — для критичных путей иногда лучше явный INSERT ... ON CONFLICT через raw() / SQLAlchemy.
11. Чек-лист
- Списки с FK — почти всегда
select_related. - Списки с M2M / reverse —
prefetch_relatedс узкимqueryset. - Пагинация на больших таблицах (
LIMIT/cursor). - Нет «магических»
.all()в API без лимита.
Дальше: Первый проект Django · Django REST Framework · тег Django