Django - Джойны после фильтрации с целью оптимизации производительности БД?

Есть модель:

class Task(models.Model):
    title = models.CharField(...)
    message = models.TextField(...)
    performer = models.ForeignKey(CustomUser, on_delete=models.CASCADE, ...)
    orgunit = models.ForeignKey('orgunits.OrgUnit', on_delete=models.CASCADE ...)
    deviation = models.ForeignKey('tasks.Deviation', on_delete=models.RESTRICT, related_name='tasks', ...)
    creator = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, ...)
    for_control = models.ManyToManyField('self', ...)
    # другие поля

Вьюха:

class TasksViewSet(viewsets.ModelViewSet):
    serializer_class = TaskSerializer
    permission_classes = [IsAuthenticated]
    pagination_class = TasksPaginator
    filterset_class = TasksFilterSet


    def get_queryset(self):
        qs = Task.objects\
            .select_related(
                'performer__position__orgunit__conformation', 'deviation',
                'creator__position__orgunit__conformation', 'orgunit__conformation',
            )

        return qs

Можно заметить, что итоговый запрос в БД будет содержать достаточно много join-ов.

Возникло несколько вопросов:

  1. насколько я знаю, данные сначала будут сджойнены, и только потом произойдет фильтрация по where? с миллион задач наберется в продовой таблице.
  2. будет ли выигрыш по производительности, если сначала отфильтровать (TasksFilterSet), а затем джойнить?

если да, то реализация ниже кошерна?

class TasksViewSet(viewsets.ModelViewSet):
    serializer_class = TaskSerializer
    permission_classes = [IsAuthenticated]
    pagination_class = TasksPaginator
    filterset_class = TasksFilterSet

    def get_queryset(self):
        return Task.objects.only('pk')

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        page = self.paginate_queryset(queryset)

        if page is not None:
            page = Task.objects.filter(pk__in=[task.pk for task in page]).select_related(
                'performer__position__orgunit__conformation', 'deviation__deviation_type_new',
                'creator__position__orgunit__conformation', 'orgunit__conformation',
            )
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        # джойны к queryset
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

насколько я знаю, данные сначала будут сджойнены, и только потом произойдет фильтрация по where?

Все сильно зависит в первую очередь от того, какая БД, во вторую от того, что у вас за условия фильтрации, но в большинстве случаев это не так.

django построит запрос, а БД выберет оптимальный план. Более продвинутые СУБД (postgres, mysql, oracle, mssql) при правильных настройках умеют хорошо оценивать, что делать лучше сначала, а что потом, каким способом лучше реализовать join при тех данных что есть в БД, тех индексах, что доступны и т.д. и планировщик выбирает исходя из этого. Вариант "join сначала, а фильтрация потом" если и будет выбран, то потому, что так действительно будет быстрей для данного запроса.

Более простые СУБД (sqlite) реализуют некий более простой и предсказуемый алгоритм, который нужно учитывать при составлении запроса и создании индексов.

будет ли выигрыш по производительности, если сначала отфильтровать (TasksFilterSet), а затем джойнить

На 100% сказать нельзя, но я готов поспорить, что будет хуже, потому что такую оптимизацию БД и сама скорее всего сделает и гораздо более эффективно, т.к. не нужно делать два отдельных похода в БД по сети, не нужно два раза сериализировать и десериализировать данные объектов Task.

Самое лучшее, что тут можно порекоммендовать - это измерить производительность на практике. Любой результат можно постфактум объяснить, но вот предсказать не всегда возможно, множество факторов и они очень неочевидным образом могут влияють на результат.

Вернуться на верх