У меня есть проблема с производительностью запросов, которую я пытаюсь решить в Django.
Среда:
- Django 2.2
- Python 3.6
- Postgresql 11
Примеры моделей:
class Location(models.Model):
name = models.CharField(max_length=256)
# ...
class VendingMachine(models.Model):
location = models.ForeignKey("MyApp.Location", on_delete=models.CASCADE)
name = models.CharField(max_length=8)
# ...
class Vend(models.Model):
vending_machine = models.ForeignKey("MyApp.VendingMachine", on_delete=models.PROTECT)
vend_start_time = models.DateTimeField(db_index=True)
# ...
Я пытаюсь получить список самых последних Vends на VendingMachine.
Есть несколько подходов, которые я выбрал, но они либо не совсем работают в моих настройках и требованиях, либо слишком долго исполняются. .
Версия 1:
Vend.objects.filter(pk__in=Subquery(Vend.objects.order_by().values('vendingmachine__location__id', 'vendingmachine__id').annotate(max_id=Max('id')).values('max_id')))
Эта версия очень быстрая. Тем не менее, это работает только в том случае, если идентификаторы Vend в хронологическом порядке. Данные вставляются в базу данных в случайном порядке, поэтому это не работает.
Версия 2:
Vend.objects.all().order_by('vendingmachine_id', '-vend_start_time').distinct('vendingmachine_id')
Эта версия занимает 12-15 секунд выполнить, и так как он выполняется через paginator, запрос выполняется дважды (один раз для подсчета, второй раз для получения объектов и нарезки), поэтому загрузка страницы занимает около 30 секунд, что слишком долго .
Другая проблема с этой версией заключается в том, что результаты не могут быть отсортированы (кроме как в Python) после того, как они возвращены, так как он полагается на order_by для сортировки vend_start_time для выбора последней.
Версия 3:
vend_sub_qs = Vend.objects.filter(vendingmachine_id=OuterRef("vendingmachine_id")).order_by("-vend_start_time").values_list("id", flat=True)[:1]
vend_qs = Vend.objects.filter(pk__in=Subquery(vend_sub_qs)).order_by("-vend_start_time")
vending_machines = VendingMachine.objects.prefetch_related(Prefetch("vend_set", queryset=vend_qs))
Я попробовал другой подход, в результате чего появился список торговых автоматов с их последними предварительно выбранными Vends. Это не очень хорошо работает, потому что мне действительно нужно завершить QuerySet of Vends.
Это также было чрезвычайно медленно и занимало около 45 секунд.
Резюме:
Важно, чтобы я завершил QuerySet объекта Vend и чтобы он мог быть отсортирован по различным полям в Vend.
Было бы идеально, если бы он мог выполняться за 5 секунд или меньше.
Можно использовать Django функции, которые Postgres специфицированы c.
Raw SQL также является опцией, если QuerySet все еще можно получить в конце.