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-ов.
Возникло несколько вопросов:
- насколько я знаю, данные сначала будут сджойнены, и только потом произойдет фильтрация по where? с миллион задач наберется в продовой таблице.
- будет ли выигрыш по производительности, если сначала отфильтровать (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.
Самое лучшее, что тут можно порекоммендовать - это измерить производительность на практике. Любой результат можно постфактум объяснить, но вот предсказать не всегда возможно, множество факторов и они очень неочевидным образом могут влияють на результат.