Django 5 для начинающих

Прогресс по курсу:  9/1004

10.6 Оптимизация SQL запросов и установка Debug-Toolbar
3 из 4 шагов пройдено
0 из 5 баллов  получено

В прошлом шаге мы уже столкнулись с тем, что при выводе записей, 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


На этом оптимизация наших запросов подошла к концу, в следующем разделе продолжим дорабатывать наш проект, путем добавления нового функционала.


Будьте вежливы и соблюдайте наши принципы сообщества. Пожалуйста, не оставляйте решения и подсказки в комментариях, для этого есть отдельный форум.
Оставить комментарий

Получается нам следует всегда переопределять работу базового менеджера используя select_related() каждый раз, когда в модели имеются внешние ключи других моделей, чтобы оптимизировать количество запросов к бд?

Изменен Шамбер Егор

@Шамбер_Егор, Верно, метод select_related загружает связанные объекты используя JOIN в запросе.