Django ORM
Django ORM поставляется "из коробки" в django и позволяет производить манипуляцию данными из БД на уровне питоновского кода. Из этого очевидна потенциальная проблема, которая возникает при выборках данных: ORM не всегда составляет оптимальные запросы при обычных выборках через .objects. Однако этот недостаток можно нивелировать за счет некоторых методов, речь о которых пойдет далее.
Проблема N+1 запросов
Одна из основных проблем проявляется при выборке вложенных моделей. Например, при такой выборке будет сформирован запрос на выборку из таблицы city для каждого author:
for a in Author.objects.all():
print(a.name, a.city.name)
В таком случае можно сформировать запрос через SQL JOIN, вызвав метод select_related:
authors = Author.objects.prefetch_related('city')
Тогда при обращениях к author.city не будут вызываться дополнительные запросы. Однако стоит учитывать то, что при JOIN размер выборки увеличивается за счет появления дополнительных атрибутов, из-за чего возрастает расход оперативной памяти на узле с СУБД.
Решение аналогичной проблемы для связей Many-to-many
Для подобной оптимизации запросов выборки для связей многие-ко-многим существует метод prefetch_related:
authors = Author.objects.prefetch_related('books')
При работе с большими объемами данных это существенно ускоряет запросы к БД, но опять же появляется проблема с оперативной памятью.
Включение/исключение полей в выборке
Оптимизировать можно не только через JOIN, но и через модификацию набора атрибутов в SELECT-запросах. Например, если нам нужно выбрать конкретное поле модели, можно использовать only:
authors = Author.objects.only('name')
Если же необходимо вызвать lazy-loading полей, можно использовать defer:
books = Book.objects.defer('content')
Этот метод особенно полезен, если в выборке присутствуют объемные текстовые поля, которые не интересуют нас при обработке.
Вынесение агрегации данных на уровень СУБД
Если вам нужно выполнить агрегацию данных (например, подсчитать количество объектов или вычислить среднее значение), Django ORM предоставляет методы annotate и aggregate. annotate добавляет вычисленные агрегаты к каждому объекту в запросе, в то время как aggregate выполняет агрегацию на уровне всего запроса. Например, посчитаем количество книг у авторов:
from django.db.models import Count
authors = Author.objects.annotate(book_count=Count('books'))
Кастомные запросы
Помимо вышеперечисленных оптимиаций Django ORM, как любая адекватная ORM позволяет использовать запросы, написанные непосредственно на SQL. Для этого используется метод extra:
authors = Author.objects.extra(
select={'custom_field': 'SELECT COUNT(*) FROM myapp_book WHERE myapp_book.author_id = myapp_author.id'}
)
Это может быть полезно для составления сложных запросов или при наличии высоких требований к производительности.