【发布时间】:2020-01-22 22:45:26
【问题描述】:
我想在 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)
# ...
我正在尝试获取每台 VendingMachine 的最新 Vends 列表。
我采用了几种方法,但它们要么在我所拥有的设置和要求中不太适用,要么执行时间太长。
版本 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 ID 按时间顺序排列时才有效。数据是按随机顺序插入到数据库中的,所以这行不通。
版本 2:
Vend.objects.all().order_by('vendingmachine_id', '-vend_start_time').distinct('vendingmachine_id')
这个版本执行需要 12-15 秒,由于是通过分页器运行的,所以查询执行了两次(一次用于计数,第二次用于获取对象和切片),因此页面需要加载大约需要 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 结尾。
这也非常慢,需要大约 45 秒才能执行。
总结:
重要的是我以 Vend 对象的 QuerySet 结尾,并且可以按 Vend 上的不同字段对其进行排序。
如果这可以在 5 秒或更短的时间内执行,那就太理想了。
可以使用 Postgres 特有的 Django 函数。
原始 SQL 也是一个选项,如果最后仍然可以获得 QuerySet。
【问题讨论】:
标签: django postgresql django-queryset