В прошлом шаге мы уже столкнулись с тем, что при выводе записей, Django выполняет три запроса. Первый запрос выбирает нашу запись, а второй запрос выбирает категорию у поста, а третий выводит автора поста. Если вы выберете N постов, чтобы отобразить их на веб-странице, вам нужно выполнить N + 1 запрос, чтобы получить как записи, так и их категории и авторов. Эта проблема известна как проблема запроса N + 1.
Что такое select_related()
Этот метод возвращает QuerySet, который будет «следовать» отношениям внешнего ключа, выбирая дополнительные данные связанного объекта при выполнении своего запроса. Это повышение производительности, которое приводит к одному более сложному запросу, но означает, что дальнейшее использование отношений внешнего ключа не потребует запросов к базе данных. select_related работает путем создания соединения (join) SQL и включения полей связанного объекта в оператор SELECT. По этой причине select_related получает связанные объекты в одном запросе к базе данных.
Чтобы решить проблему с запросом N + 1, вы можете использовать select_related() метод выбора как постов, так и разделов с помощью одного запроса. Например:
Post.objects.select_related('category').all()
Давайте от теории перейдем к практике и оптимизируем наши запросы в менеджере модели Post:
class PostManager(models.Manager):
"""
Кастомный менеджер для модели постов
"""
def get_queryset(self):
"""
Список постов (SQL запрос с фильтрацией по статусу опубликованно)
"""
return super().get_queryset().select_related('author', 'category').filter(status='published')
К методу кастомного менеджера модели добавим до фильтрации по статусу метод: select_related() для ключа Foreign Key - Автора и Категорий, так как со статьи мы ссылаемся на автора и категорию.
Давайте посмотрим результат оптимизации, мы видим что количество запросов к БД уменьшилось, вместо 9 их стало 5. И дубликатов запросов нет, вместо этого стал один сложный запрос:
SELECT "blog_post"."id",
"blog_post"."title",
"blog_post"."slug",
"blog_post"."description",
"blog_post"."text",
"blog_post"."category_id",
"blog_post"."thumbnail",
"blog_post"."status",
"blog_post"."create",
"blog_post"."update",
"blog_post"."author_id",
"blog_post"."updater_id",
"blog_post"."fixed",
"app_categories"."id",
"app_categories"."title",
"app_categories"."slug",
"app_categories"."description",
"app_categories"."parent_id",
"app_categories"."lft",
"app_categories"."rght",
"app_categories"."tree_id",
"app_categories"."level",
"auth_user"."id",
"auth_user"."password",
"auth_user"."last_login",
"auth_user"."is_superuser",
"auth_user"."username",
"auth_user"."first_name",
"auth_user"."last_name",
"auth_user"."email",
"auth_user"."is_staff",
"auth_user"."is_active",
"auth_user"."date_joined"
FROM "blog_post"
INNER JOIN "app_categories"
ON ("blog_post"."category_id" = "app_categories"."id")
INNER JOIN "auth_user"
ON ("blog_post"."author_id" = "auth_user"."id")
WHERE "blog_post"."status" = '''published'''
ORDER BY "blog_post"."fixed" DESC,
"blog_post"."create" DESC
LIMIT 2
На этом оптимизация наших запросов подошла к концу, в следующем разделе продолжим дорабатывать наш проект, путем добавления нового функционала.