Ответ
Основная цель оптимизации — сокращение количества запросов к базе данных (решение проблемы "N+1") и уменьшение объема передаваемых данных.
-
*`select_related(fields)
** Оптимизирует запросы к связанным объектам черезForeignKeyилиOneToOne. Он "подтягивает" связанные данные однимSQL JOIN` запросом, предотвращая дополнительные запросы в цикле.# Плохо: N+1 запросов (1 для постов + N для авторов) for post in Post.objects.all(): print(post.author.name) # Хорошо: 1 запрос с JOIN for post in Post.objects.select_related('author').all(): print(post.author.name) -
*`prefetch_related(lookups)
** Используется дляManyToManyFieldи обратныхForeignKey. В отличие отselect_related, он делает отдельный запрос для связанных объектов и "склеивает" данные в Python. Это эффективнее, чемJOIN` для связей "многие-ко-многим".# 1 запрос для постов + 1 запрос для всех тегов posts = Post.objects.prefetch_related('tags').all() -
only(*fields)иdefer(*fields)Позволяют загружать только указанные поля модели (only) или все, кроме указанных (defer). Это сокращает объем передаваемых данных и потребление памяти.# Загрузить только заголовки titles = Post.objects.only('title') -
values()иvalues_list()Возвращают словари или кортежи вместо полноценных объектов моделей. Это значительно быстрее, если вам нужны только данные и не требуются методы модели. -
annotate()иaggregate()Переносят вычисления (например,COUNT,SUM,AVG) на сторону базы данных, что намного эффективнее, чем делать это в Python.from django.db.models import Count # Получить авторов с количеством их постов authors = Author.objects.annotate(post_count=Count('posts')) -
exists()иcount()Для проверки наличия записи используйтеexists(), а для подсчета —count(). Они выполняют более легковесные SQL-запросы, чем загрузка всего QuerySet. -
bulk_create()иbulk_update()Для массового создания или обновления объектов используйте эти методы, чтобы выполнить операцию одним запросом вместо множества.
Рекомендация: Всегда анализируйте запросы с помощью django-debug-toolbar или connection.queries для выявления узких мест.
Ответ 18+ 🔞
А, слушай, вот это тема, про которую я могу говорить часами, потому что каждый второй проект на Django спотыкается об одно и то же — про ебаную оптимизацию запросов! Ну, точнее, про её полное отсутствие. Люди пишут код, а потом удивляются, почему страница грузится как черепаха в сиропе. А всё из-за классической хуйни под названием "Проблема N+1". Представь: ты получаешь список постов, а потом в цикле для каждого поста лезешь в базу за автором. Один запрос за постами, и ещё N запросов за авторами. Пиздец, а не архитектура.
Ну ладно, хватит ныть, давай по делу. Django, он хоть и медленный на вид, но даёт нам кучу инструментов, чтобы не выглядеть полными даунами.
1. select_related(*fields)
Это твой лучший друг для связей ForeignKey или OneToOne. Он делает один здоровенный JOIN в SQL и сразу притаскивает все связанные данные. Вместо того чтобы дёргать базу на каждом чихе.
# Пиздец как плохо: запрос за постами, а потом для каждого поста — отдельный запрос за автором. N+1 в чистом виде.
for post in Post.objects.all():
print(post.author.name) # ОПА! Новый запрос в БД на каждой итерации!
# А вот так — красиво и по-взрослому. Один запрос, и все авторы уже прицеплены.
for post in Post.objects.select_related('author').all():
print(post.author.name) # Данные уже в памяти, нихуя не грузим!
2. prefetch_related(*lookups)
А это уже для более сложных связей — ManyToManyField или обратных ForeignKey. JOIN тут может превратиться в ад, поэтому prefetch_related действует хитрее: делает два запроса (один за основной моделью, второй за связанной), а потом в Питоне всё аккуратно склеивает. Умно, блядь.
# Всего 2 запроса: один для всех постов, второй — для ВСЕХ их тегов. Никакого N+1.
posts = Post.objects.prefetch_related('tags').all()
for post in posts:
for tag in post.tags.all(): # Теги уже здесь, в памяти!
print(tag.name)
3. only(*fields) и defer(*fields)
Зачем тащить из базы всю хуйню, если нужна только пара полей? Особенно если в модели есть здоровенные TextField. only загружает только указанные поля, defer — наоборот, всё, кроме указанных. Экономия трафика и памяти — овердохуищная.
# Загрузим только заголовки, а всё остальное — нахуй не надо.
titles = Post.objects.only('title')
4. values() и values_list()
А если тебе вообще не нужен объект модели со всеми его методами и прибамбасами? Если нужны просто голые данные? Вот тут эти методы спасают — возвращают словарики или кортежи. Скорость вырастает в разы, потому что ORM не тратит время на создание объектов.
# Просто список словарей с id и title. Быстро и сердито.
post_data = Post.objects.values('id', 'title')
5. annotate() и aggregate()
Это чтобы не быть идиотом и не считать что-то в цикле на Питоне. База данных считает в сотни раз быстрее. annotate — добавляет вычисляемое поле к каждому объекту (например, количество комментов). aggregate — считает общую статистику по всему QuerySet (сумму, среднее и т.д.).
from django.db.models import Count
# Каждому автору добавим поле post_count. Всё считается одним запросом на стороне БД.
authors = Author.objects.annotate(post_count=Count('posts'))
6. exists() и count()
Классика: нужно проверить, есть ли хоть одна запись? Не делай if queryset:, блядь! Это выполнит запрос и загрузит ВСЕ данные. Используй exists() — он сделает легковесный SELECT 1 ... LIMIT 1. Аналогично с подсчётом — count() сделает SELECT COUNT(*), а не SELECT *.
# Пиздец как эффективно
if SomeModel.objects.filter(condition).exists():
print("Что-то есть, ёпта!")
total = SomeModel.objects.count() # Быстро и без лишней нагрузки.
7. bulk_create() и bulk_update()
Ну это вообще must-have, когда нужно впихнуть или обновить кучу записей. Вместо тысячи отдельных INSERT или UPDATE — один здоровенный запрос. Разница в скорости — небо и земля, ядрёна вошь.
# Создаём 1000 объектов одним махом
objects_to_create = [MyModel(name=f'Object {i}') for i in range(1000)]
MyModel.objects.bulk_create(objects_to_create)
И главная рекомендация, блядь: Не гадай на кофейной гуще! Поставь django-debug-toolbar или смотри в connection.queries. Увидишь, какие запросы летят, сколько их и как долго выполняются. А то будешь как тот Герасим — молча страдать, а проблема-то в ебучем N+1, который на виду. Не будь немым, оптимизируй!