【问题标题】:Django .count() on ManyToMany has become very slowManyToMany 上的 Django .count() 变得非常慢
【发布时间】:2018-07-13 10:55:44
【问题描述】:

我有一个 Django 项目,它由我们的库存刮板组成,每隔几个小时在服务器上作为 cronjob 运行,以及 Django 管理页面 - 我们用来查看/访问所有项目。

我们有大约 30 个已编入索引的项目。 因此,每个“抓取操作”由大约 30 个单独的“搜索操作”组成,每个“搜索操作”每次运行得到大约 500 个结果。

现在,这个描述有点混乱,所以我在下面列出了模型。

class ScrapingOperation(models.Model):
    date_started = models.DateTimeField(default=timezone.now, editable=True)
    date_completed = models.DateTimeField(blank=True, null=True)
    completed = models.BooleanField(default=False)
    round = models.IntegerField(default=-1)
    trusted = models.BooleanField(default=True)


class Search(models.Model):
    item = models.ForeignKey(Item, on_delete=models.CASCADE)
    date_started = models.DateTimeField(default=timezone.now, editable=True)
    date_completed = models.DateTimeField(blank=True, null=True)
    completed = models.BooleanField(default=False)
    round = models.IntegerField(default=1)
    scraping_operation = models.ForeignKey(ScrapingOperation, on_delete=models.CASCADE, related_name='searches')
    trusted = models.BooleanField(default=True)

    def total_ads(self):
        return self.ads.count()


class Ad(models.Model): 

  item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='ads')    
  title = models.CharField(max_length=500)
  price = models.DecimalField(max_digits=8, decimal_places=2, null=True)         
  first_seen = models.DateTimeField(default=timezone.now, editable=True)
  last_seen = models.DateTimeField(default=timezone.now, editable=True)

  def __str__(self):
      return self.title

现在这是我们遇到的问题。

在 Search 模型和 SeachOperation 模型的管理页面上,我们希望看到为该特定对象抓取的广告数量(表示为数字)这对我们的四个搜索者来说效果很好,但是我们对 SearchOperation 的实现有遇到问题

这是我们使用的代码:

class ScrapingOperationAdmin(admin.ModelAdmin):
    list_display = ['id', 'completed', 'trusted', 'date_started', 'date_completed', 'number_of_ads']
    list_filter = ('completed', 'trusted')
    view_on_site = False

    inlines = [
        SearchInlineAdmin,
    ]

    def number_of_ads(self, instance):
        total_ads = 0
        for search in instance.searches.all():
            total_ads += search.ads.count()
        return total_ads

我们遇到的问题是:代码有效并提供了正确的数字,但是,在 +/- 10 ScrapingOperation 之后,我们注意到网站在加载页面时开始变慢。我们现在有多达 60 个 ScrapingOperations,当我们在 Django 管理员中单击 ScrapingOperations 页面时,加载几乎需要一分钟。

有没有更有效的方法来做到这一点?我们考虑将广告总数保存到模型本身,但是将一个字段专用于应该通过简单的 .count() 调用访问的信息似乎很浪费。然而,我们的查询显然效率很低,以至于整个站点在执行时锁定了将近一分钟。有谁知道我们做错了什么?

基于下面的 cmets,我目前正在研究以下解决方案:

def number_of_ads(self, instance):
    total_ads = 0
    searches = Search.objects.filter(scraping_operation=instance).annotate(Count('ads'))
    for search in searches:
        total_ads += search.ads__count
    return total_ads

【问题讨论】:

标签: django django-admin django-queryset


【解决方案1】:

获取查询集时使用注解

from django.db.models import Count
class ScrapingOperationAdmin(admin.ModelAdmin):
     ...
     def get_queryset(self, request):
           qs = super().get_queryset(request)
           qs.annotate(number_of_ads=Count('searches__ads')
           return qs

【讨论】:

  • qs 会自动填充“number_of_ads”字段吗? (意思是我可以从类中删除 'number_of_ads' 方法?)
  • 是的 number_of_ads 将是一个字段。
猜你喜欢
  • 2011-05-25
  • 1970-01-01
  • 2012-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-04
  • 1970-01-01
  • 2013-04-10
相关资源
最近更新 更多