Django:有效地从ManyToMany关系中检索单个对象

时间:2017-05-03 05:55:53

标签: django django-models django-queryset database-optimization

我有一个数据库结构,可以简化如下(版本和附加信息未显示,因为与我的问题没有直接关系):

class Image(models.Model):    
    file = models.ImageField(blank=False, null=False, upload_to='images')
    version = models.ForeignKey(Version, blank=False, null=False)

class ExampleImage(models.Model):
    example = models.ForeignKey('Example', blank=False, null=False)
    image = models.ForeignKey(Image, blank=False, null=False)
    additional_info = models.ForeignKey(AdditionalInfo, blank=True, null=True)

class Example(models.Model):
    name = models.Charfield(max_length=255)
    images = models.ManyToManyField(Image, through=ExampleImage, through_fields=('example', 'image')

    def get_default_image(self):
        try:
            image = self.images.get(version=Version.objects.get(name=DEFAULT_VERSION))
        except Image.DoesNotExist:
             # Get some other image, this happens maybe 1 out of 10

现在我非常需要查询所有Example对象及其默认图像。目前,我有点喜欢以下内容:

example_list = []
examples = Example.objects.all()

for example in examples:
    example_dict = dict(name=example.name, image=example.get_default_image())
    example_list.append(example_dict)

# Then show example_list in template

这种方法有效但会导致成千上万的数据库查询,并且需要花费一分多钟来执行,这对于下载单个网页来说太长了!

所以我的问题是,优化这种用例的正确方法是什么。我可以为Example模型设置default_image字段(然后使用select_related,但这会使图像更复杂),将版本ID硬编码为get_default_image方法(不那么灵活),将get_default_image方法设置为cached_property等但是我&#39 ;我不确定应该使用哪种方法。我已经尝试了很多技巧,但似乎没有任何帮助我的情况。我应该找到一个解决方案,它允许我用一个大查询查询示例和默认图像,而不是在for循环中进行工作。

1 个答案:

答案 0 :(得分:1)

DEFAULT_VERSION模块是否常数?如果是这样,您可以将version=Version.objects.get(name=DEFAULT_VERSION)调用分解出去,这样您就不必为每个example in examples执行此操作。

您还应该能够使用自定义Prefetch对象:https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.Prefetch。有些事情(虽然你必须自己测试和调整):

qs = Image.filter(version=Version.objects.get(name=DEFAULT_VERSION))
pref = Prefetch('images', queryset=qs, to_attr='image')
examples = Example.objects.prefetch_related(pref)

理想情况下,您当然会将Example.default_image设置为OneToOneField,以便您可以有效地检索默认图像。

cached_property装饰器在这里不会真正帮助你,因为只要实例存在,它只会缓存属性。如果您需要多次调用instance.get_default_image() 会有所帮助,但您仍然需要为您获取的每个实例执行一次get_default_image

希望有所帮助