Django 预取与最大值过滤器相关
Django Prefetch Related with Filter on Max Value
我们有一对模型(大致)如下所示:
class Machine(models.Model):
machine_id = models.CharField(max_length=10)
# Other irrelevant fields
@property
def latest_update(self):
if self.machineupdate_set.count() == 0:
return None
return self.machineupdate_set.order_by('-update_time')[:1].get()
class MachineUpdate(models.Model):
machine = models.ForeignKey(Machine)
update_time = models.DateTimeField(auto_now_add=True)
# Other irrelevant fields
每当我们从数据库加载 Machine
s 时,我们总是最终使用该机器的 latest_update
。当我们第一次实现它时,我们有很多机器,每台机器的更新数量相当少,因此为了提高性能(通过减少查询计数),我们为 Machine
的模型管理器添加了一个简单的默认预取:
class MachineManager(models.Manager):
def get_queryset(self):
return super(MachineManager, self).get_queryset().prefetch_related('machineupdate_set')
但是,情况发生了变化,现在我们有大量与每台机器相关的更新,预取查询开始成为一个问题(在较长的查询执行时间和内存消耗方面)。
我们正在寻找一种更智能的方式来预取所需的数据,因为我们真正需要预取的只是每台机器的最新更新,而不是全部.查看 Django prefetch_related docs 后,我们似乎可以将 MachineManager
中的 get_queryset
更改为如下内容:
def get_queryset(self):
latest_update_query = MachineUpdate.objects.order_by('-update_time')[:1]
latest_update_prefetch = models.Prefetch('machineupdate_set', queryset=latest_update_query, to_attr='_latest_update')
return super(MachineManager, self).get_queryset().prefetch_related(latest_update_prefetch)
然后修改 latest_update
以使用预取填充的新属性。但是,这不起作用,因为每当我们使用它过滤 Machine
查询时,我们都会收到错误消息:AssertionError: Cannot filter a query once a slice has been taken.
任何人都可以提出解决这个问题的方法,以便我们可以有效地为每台机器加载 latest_update
吗?我们不确定如何解决上述尝试预取最新更新时遇到的问题。
(仅供参考 - 我们考虑过向 MachineUpdate
添加一个 is_latest_update
布尔字段,我们可以对其进行过滤,或者在 Machine
上添加一个 latest_update
外键引用,但是我们希望避免必须维护这些冗余信息)。
我看到 MachineUpdate.update_time
有 auto_now_add=True
。所以我们可以使用每个 Machine
组的 Max(MachineUpdate.id)
来获得最后一个 MachineUpdate
。正确的?如果是 True
检查以下代码:
class MachineManager(models.Manager):
pass
class MachineQueryset(models.QuerySet):
def with_last_machineupdate(self):
return self.prefetch_related(models.Prefetch('machineupdate_set',
queryset=MachineUpdate.objects.filter(
id__in=Machine.objects \
.annotate(last_machineupdate_id=models.Max('machineupdate__id')) \
.values_list('last_machineupdate_id', flat=True) \
),
#notice the list word
to_attr='last_machineupdate_list'
))
class Machine(models.Model):
machine_id = models.CharField(max_length=10)
objects = MachineManager.from_queryset(MachineQueryset)()
@property
def latest_update(self):
if hasattr(self, 'last_machineupdate_list') and len(self.last_machineupdate_list) > 0:
return self.last_machineupdate_list[0]
return None
class MachineUpdate(models.Model):
machine = models.ForeignKey(Machine)
update_time = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return str(self.update_time)
用法:
machines = Machine.objects.filter(...).with_last_machineupdate()
如果不是这样,例如我们不能使用 Max('machineupdate__id')
,我们需要坚持使用 update_time
字段。然后一个稍微优化的解决方案(但仍然得到每个 Machine
的所有 MachineUpdates
)看起来像这样:
class MachineManager(models.Manager):
def get_queryset(self):
return super(MachineManager, self).get_queryset() \
.prefetch_related(models.Prefetch('machineupdate_set',
queryset=MachineUpdate.objects.order_by('-update_time')
))
class Machine(models.Model):
machine_id = models.CharField(max_length=10)
objects = MachineManager()
@property
def latest_update(self):
#this will not make queries
machine_updates = self.machineupdate_set.all()
if len(machine_updates) > 0:
return machine_updates[0]
return None
我们有一对模型(大致)如下所示:
class Machine(models.Model):
machine_id = models.CharField(max_length=10)
# Other irrelevant fields
@property
def latest_update(self):
if self.machineupdate_set.count() == 0:
return None
return self.machineupdate_set.order_by('-update_time')[:1].get()
class MachineUpdate(models.Model):
machine = models.ForeignKey(Machine)
update_time = models.DateTimeField(auto_now_add=True)
# Other irrelevant fields
每当我们从数据库加载 Machine
s 时,我们总是最终使用该机器的 latest_update
。当我们第一次实现它时,我们有很多机器,每台机器的更新数量相当少,因此为了提高性能(通过减少查询计数),我们为 Machine
的模型管理器添加了一个简单的默认预取:
class MachineManager(models.Manager):
def get_queryset(self):
return super(MachineManager, self).get_queryset().prefetch_related('machineupdate_set')
但是,情况发生了变化,现在我们有大量与每台机器相关的更新,预取查询开始成为一个问题(在较长的查询执行时间和内存消耗方面)。
我们正在寻找一种更智能的方式来预取所需的数据,因为我们真正需要预取的只是每台机器的最新更新,而不是全部.查看 Django prefetch_related docs 后,我们似乎可以将 MachineManager
中的 get_queryset
更改为如下内容:
def get_queryset(self):
latest_update_query = MachineUpdate.objects.order_by('-update_time')[:1]
latest_update_prefetch = models.Prefetch('machineupdate_set', queryset=latest_update_query, to_attr='_latest_update')
return super(MachineManager, self).get_queryset().prefetch_related(latest_update_prefetch)
然后修改 latest_update
以使用预取填充的新属性。但是,这不起作用,因为每当我们使用它过滤 Machine
查询时,我们都会收到错误消息:AssertionError: Cannot filter a query once a slice has been taken.
任何人都可以提出解决这个问题的方法,以便我们可以有效地为每台机器加载 latest_update
吗?我们不确定如何解决上述尝试预取最新更新时遇到的问题。
(仅供参考 - 我们考虑过向 MachineUpdate
添加一个 is_latest_update
布尔字段,我们可以对其进行过滤,或者在 Machine
上添加一个 latest_update
外键引用,但是我们希望避免必须维护这些冗余信息)。
我看到 MachineUpdate.update_time
有 auto_now_add=True
。所以我们可以使用每个 Machine
组的 Max(MachineUpdate.id)
来获得最后一个 MachineUpdate
。正确的?如果是 True
检查以下代码:
class MachineManager(models.Manager):
pass
class MachineQueryset(models.QuerySet):
def with_last_machineupdate(self):
return self.prefetch_related(models.Prefetch('machineupdate_set',
queryset=MachineUpdate.objects.filter(
id__in=Machine.objects \
.annotate(last_machineupdate_id=models.Max('machineupdate__id')) \
.values_list('last_machineupdate_id', flat=True) \
),
#notice the list word
to_attr='last_machineupdate_list'
))
class Machine(models.Model):
machine_id = models.CharField(max_length=10)
objects = MachineManager.from_queryset(MachineQueryset)()
@property
def latest_update(self):
if hasattr(self, 'last_machineupdate_list') and len(self.last_machineupdate_list) > 0:
return self.last_machineupdate_list[0]
return None
class MachineUpdate(models.Model):
machine = models.ForeignKey(Machine)
update_time = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return str(self.update_time)
用法:
machines = Machine.objects.filter(...).with_last_machineupdate()
如果不是这样,例如我们不能使用 Max('machineupdate__id')
,我们需要坚持使用 update_time
字段。然后一个稍微优化的解决方案(但仍然得到每个 Machine
的所有 MachineUpdates
)看起来像这样:
class MachineManager(models.Manager):
def get_queryset(self):
return super(MachineManager, self).get_queryset() \
.prefetch_related(models.Prefetch('machineupdate_set',
queryset=MachineUpdate.objects.order_by('-update_time')
))
class Machine(models.Model):
machine_id = models.CharField(max_length=10)
objects = MachineManager()
@property
def latest_update(self):
#this will not make queries
machine_updates = self.machineupdate_set.all()
if len(machine_updates) > 0:
return machine_updates[0]
return None