Ответ
Основные проблемы производительности в Django ORM связаны с генерацией избыточных или неоптимальных SQL-запросов. Вот ключевые из них и способы их решения:
-
Проблема N+1 запросов
- Суть: При итерации по QuerySet для каждого объекта выполняется дополнительный запрос для получения связанных данных.
- Решение: Использовать
select_related(дляForeignKey,OneToOneField) иprefetch_related(дляManyToManyField,GenericForeignKeyи обратных связей), чтобы загрузить связанные данные одним или двумя запросами.
# ПЛОХО: 1 запрос за книгами + N запросов за авторами for book in Book.objects.all(): print(book.author.name) # Отдельный SQL-запрос на каждой итерации # ХОРОШО: 1 запрос, объединяющий книги и авторов через JOIN for book in Book.objects.select_related('author').all(): print(book.author.name) -
Загрузка ненужных полей
- Суть: Выборка всех полей модели (
SELECT *), когда требуется только несколько из них. Это увеличивает нагрузку на сеть и потребление памяти. - Решение: Использовать
only()для указания только необходимых полей илиdefer()для исключения тяжелых полей.
- Суть: Выборка всех полей модели (
-
Вычисление
.count()или проверка существования в Python- Суть: Использование
len(queryset)загружает все объекты в память только для подсчета их количества. Аналогично,if queryset:загружает данные для проверки на пустоту. - Решение: Использовать
queryset.count()для выполнения эффективногоSELECT COUNT(*)в базе данных иqueryset.exists()дляSELECT ... LIMIT 1, что намного быстрее.
- Суть: Использование
-
Обновление и создание объектов в цикле
- Суть: Выполнение
save()илиcreate()в цикле генерирует отдельный SQL-запрос (INSERTилиUPDATE) для каждого объекта. - Решение: Использовать
bulk_create()иbulk_update()для выполнения массовых операций одним запросом.
- Суть: Выполнение
-
Отсутствие индексов в базе данных
- Суть: Медленные запросы с фильтрацией (
filter,exclude) или сортировкой (order_by) по полям, которые не проиндексированы в БД. - Решение: Добавить
db_index=Trueк соответствующим полям в модели Django и выполнить миграции.
- Суть: Медленные запросы с фильтрацией (
Для обнаружения этих проблем рекомендуется использовать Django Debug Toolbar, который наглядно показывает все выполненные SQL-запросы, их количество и время выполнения.
Ответ 18+ 🔞
О, смотри-ка, какие у нас тут проблемы с производительностью в ORM подъехали! Ну, это же классика, блядь. Все через это проходят, а потом сидят и чешут репу, почему приложение тормозит, как черепаха в сиропе. Давай разберём, что за хуйня тут творится и как с этим бороться, чтобы не охуеть от количества запросов.
Первое и самое жирное — это проблема N+1 запросов. Сука, просто пиздец, как часто её встречаю. Представь: ты получаешь список книг, а потом на каждой итерации лезешь за автором отдельно. База данных просто обоссывается от такого наглого обращения. Вместо одного красивого JOIN'а — куча мелких тычков, как назойливый комар.
# ПЛОХО: Сначала один запрос за книгами, а потом на каждый чих — ещё один за автором. Пидарас шерстяной!
for book in Book.objects.all():
print(book.author.name) # И вот тут, на каждой строчке, новый запрос. Овердохуища!
# ХОРОШО: Всё за один присест, одним махом, как говорится. Красота!
for book in Book.objects.select_related('author').all():
print(book.author.name)
Второй момент — загрузка ненужных полей. Зачем тащить из базы все колонки, включая это огромное текстовое поле на десять мегабайт, если тебе нужно только название и дата? Это же, блядь, как приехать за хлебушком на грузовике-самосвале. Используй only() или defer(), не будь распиздяем.
Третья ловушка — подсчёт и проверки на питоне. Ты что, в натуре, думаешь, что len(queryset) — это магия? Он сначала ВСЕ записи в память загрузит, сука, а потом посчитает! Да ты что, охренел? База данных умеет считать сама, для этого есть count(). А чтобы просто проверить, есть ли хоть что-то, — exists(). Не изобретай велосипед, блядь.
Четвёртый грех — сохранение в цикле. Ну, ясен пень, если ты в цикле вызываешь save() для каждого объекта, то на каждый чих будет отдельный INSERT или UPDATE. База аж взвоет. Тут тебе на помощь приходят bulk_create() и bulk_update(). Закинул всё в кучу — и одним запросом отправил. Просто, как три копейки.
И последнее, но не по важности — отсутствие индексов. Ты фильтруешь и сортируешь по полям, а они не проиндексированы. Это как искать иголку в стоге сена в полной темноте, ёпта. Добавь db_index=True в модель и не мучай ни базу, ни себя.
А чтобы не гадать на кофейной гуще, где же эта засада, — поставь Django Debug Toolbar. Эта мартышлюшка покажет тебе все запросы, их время и количество. Глаза на лоб полезут, когда увидишь, какую хуйню твой код отжигает.