2016-03-23 112 views
4

我有非常大的数据集并且不断增长,我需要创建多个过滤器,但它很快就会失去控制,希望有人能够帮助我将某些查询合并到一个调用中。以下是我的观点的开始。减少Django数据库查询

呼叫#1 - 为环,以显示所有结果

的表traffic = Traffic.objects.all()

呼叫#2 - 组合的聚合和查询

totals = Traffic.objects.aggregate(Sum('sessions'), Sum('new_users'), Sum('reminder'), Sum('campaigns'), Sum('new_sales'), Sum('sales_renewals')) 
    total_sessions = totals.get('sessions__sum') 
    total_new_users = totals.get('new_users__sum') 
    total_reminder = totals.get('reminder__sum') 
    total_campaigns = totals.get('campaigns__sum') 
    total_new_sales = totals.get('new_sales__sum') 
    total_sales_renewals = totals.get('sales_renewals__sum') 

呼叫#3,# 4,#5,#6等... - 按月份和日期过滤数据库

total_sessions_2014_m = Traffic.objects.filter(created__year='2014', created__week_day=2).aggregate(Sum('sessions')) 

total_sessions_2014_m = Traffic.objects.filter(created__year='2014', created__week_day=3).aggregate(Sum('sessions')) 

total_sessions_2014_m = Traffic.objects.filter(created__year='2014', created__week_day=4).aggregate(Sum('sessions')) 

total_sessions_2014_m = Traffic.objects.filter(created__year='2014', created__week_day=5).aggregate(Sum('sessions')) 

total_sessions_2014_m = Traffic.objects.filter(created__year='2014', created__week_day=6).aggregate(Sum('sessions')) 

问题是,我需要创建几十个更多的过滤器,因为我有3年的数据每列有多个数据点,我们需要合计总和。

问题:

  1. 我可以结合呼叫#1至#调用2
  2. 我可以使用呼叫#2查询呼叫#3的款项,所以我不必都称呼数据库中的对象来过滤它,然后再做几次?

正如你所看到的,这将很快失去控制。任何帮助将非常感激。谢谢。

更新添加 流量模型

class Timestamp(models.Model): 
    created = models.DateField() 

    class Meta: 
     abstract = True 


class Traffic(Timestamp): 
    sessions = models.IntegerField(blank=True, null=True) 
    new_users = models.IntegerField(blank=True, null=True) 
    reminder = models.IntegerField(blank=True, null=True) 
    campaigns = models.IntegerField(blank=True, null=True) 
    new_sales = models.IntegerField(blank=True, null=True) 
    sales_renewals = models.IntegerField(blank=True, null=True) 

    # Meta and String 
    class Meta: 
     verbose_name = 'Traffic' 
     verbose_name_plural = 'Traffic Data' 

    def __str__(self): 
     return "%s" % self.created 
+0

是否所有这些数据一次显示在模板上? –

+0

你可以通过添加“交通”模型来更新你的问题吗? –

+0

@ ParagTyagi-morpheus-是的,这些都是相同的观点。它是我内部营销部门的工具,将从我们的内部服务器提供。这只是冰山一角,我有大量的数据从Excel移走,因此不同意见不是一种选择。刚刚更新了我的“交通”模型。 –

回答

5

有几十种方法来优化与Django的ORM数据库查询。像往常一样,Django documentation是伟大的,并有一个很好的名单。下面是查询优化的一些快速提示:

1)iterator()

如果您正在访问的queryset只有一次。因此,例如,你可以以此为,

traffic = Traffic.objects.all() 

for t in traffic.iterator(): 
    ... 
    ... 

2)db_index=True

在定义你的models的领域使用。作为Django documentation说,

这是一个首要任务,你已经从 确定剖析应该添加哪些指标之后。使用Field.db_index或 Meta.index_together从Django中添加这些。考虑将索引 添加到您经常使用filter(),exclude(), order_by()等查询的字段,因为索引可能有助于加快查找速度。

因此,你可以修改你的模型,

class Traffic(Timestamp): 
    sessions = models.IntegerField(blank=True, null=True, db_index=True) 
    new_users = models.IntegerField(blank=True, null=True, db_index=True) 
    reminder = models.IntegerField(blank=True, null=True, db_index=True) 
    campaigns = models.IntegerField(blank=True, null=True, db_index=True) 
    new_sales = models.IntegerField(blank=True, null=True, db_index=True) 

3)prefetch_related()select_related()

如果您有models内部的关系,用prefetch_relatedselect_related将是一个选择。根据Django documentation,

select_related通过创建SQL join并包括SELECT语句中相关对象的字段来工作。因此,select_related在相同的数据库查询中获取相关对象。但是,为避免加入跨越“多”关系而导致的更大结果集,select_related仅限于单值关系 - 外键和一对一。

prefetch_related另一方面,为每个 关系做单独的查找,并在Python中进行“连接”。这允许它预取 多对多和多对一对象,除了select_related支持的外键和一对一关系之外,这些对象不能使用 select_related来完成。

select_related做了一个joinprefetch_related做两个单独的查询。使用这些可以使查询速度提高30%。


4)Django Pagination

如果您template设计,让你在显示多个页面的结果您可以使用Pagination


5)Querysets are Lazy

您还需要了解的是,Django的查询集都懒的,这意味着它不会查询数据库,直到使用它的/评估。 Django中的查询集表示数据库中的多个行,可以通过查询进行过滤。例如,

traffic = Traffic.objects.all() 

上述代码不运行任何数据库查询。您可以采取traffic查询集并应用其他过滤器,或将其传递给函数,并且不会向数据库发送任何内容。这很好,因为查询数据库是显着减慢Web应用程序速度的一个因素。为了从数据库读取的数据,你需要遍历查询集:

for t in traffic.iterator(): 
    print(t.sessions) 

6)django-debug-toolbar

的Django调试工具栏是一组可配置显示有关的各种调试信息板当前的请求/响应以及单击时显示有关面板内容的更多详细信息。这包括:

  • 请求定时器
  • SQL查询,包括执行时间和链接来解释每个查询

修改代码:(记住查询集是懒惰

traffic = Traffic.objects.all() 
totals = traffic.aggregate(Sum('sessions'), Sum('new_users'), Sum('reminder'), Sum('campaigns'), Sum('new_sales'), Sum('sales_renewals')) 
total_sessions = totals.get('sessions__sum') 
total_new_users = totals.get('new_users__sum') 
total_reminder = totals.get('reminder__sum') 
total_campaigns = totals.get('campaigns__sum') 
total_new_sales = totals.get('new_sales__sum') 
total_sales_renewals = totals.get('sales_renewals__sum') 

t_2014 = traffic.filter(created__year='2014') 
t_sessions_2014_wd2 = t_2014.filter(created__week_day=2).aggregate(Sum('sessions')) 
... 
... 

对于呼叫#1模板(for循环来显示所有结果的表):

{% for t in traffic.iterator %} 
    {{ t.sessions }} 
    ... 
    ... 
{% endfor %} 
+0

哇,谢谢你很多很棒的信息!我刚到办公室,需要一些人来消费,但会报告事情的进展情况以及最终的解决方案。 –

+0

我已经在使用django-debug-toolbar,但是使用iterator(),我能够减少一些查询。我当然需要继续调整以尽量减少我的疑问,但这使我朝着正确的方向前进。感谢你的详细回复......这让我去研究其他我不知道的领域。 –

1

至于问题1,它不应该是重用从第一次调用查询集的一个问题。

traffic = Traffic.objects.all() 
totals = traffic.aggregate(Sum('sessions'), Sum('new_users'), Sum('reminder'), Sum('campaigns'), Sum('new_sales'), Sum('sales_renewals')) 

这应该可以让您额外调用数据库。

关于问题2,您可以再次在第一次调用中重用查询集,并筛选年份,从而为您提供新的查询集,例如

traffic_2014 = traffic.filter(created__year='2014') 

然后,您可以继续过滤天,这个新的查询集聚合,像你以前那样,或每天创造新的查询集,假设你每天都聚集多个属性,从而节省您的另一十几数据库调用。

我希望这可以帮助你。

1

没有直接解决问题,但我认为你应该考虑一种不同的方法。根据我的理解

  • 视图可经常提出要求。
  • 数据应该很少变化。
  • 有必要进行复杂的数据操作(求和按年,月,日等领域)

没有必要进行,每当有人请求查看相同的查询。

在一个步骤中加载所有数据并在视图内执行操作。您可以使用像Pandas这样的库并创建复杂的数据集。该视图现在将与CPU绑定,因此请使用像Redis这样的缓存系统来避免重新计算。数据更改时无效。

另一种方法:通过使用类似Celery的任务队列定期执行计算并填充Redis。