【问题标题】:Reducing Django Database Queries减少 Django 数据库查询
【发布时间】:2016-07-10 08:24:48
【问题描述】:

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

调用 #1 - for 循环以显示所有结果的表格

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. 我是否可以使用 Call #2 来查询 call#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

【问题讨论】:

  • 所有这些数据是否一次显示在模板上?
  • 您能否通过添加Traffic 模型来更新您的问题?
  • @ParagTyagi-morpheus- 是的,这些将在同一个视图中。它是我内部营销部门的工具,将由我们的内部服务器提供服务。这只是冰山一角,我有大量数据要从 Excel 中移动,因此不能选择不同的视图。刚刚更新了我的traffic 模型。

标签: python django python-3.x django-templates django-views


【解决方案1】:

有很多方法可以使用 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 查询,包括执行时间和指向 EXPLAIN 每个查询的链接

修改你的代码:(记住查询集是惰性的

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​​strong>(for循环显示所有结果的表格):

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

【讨论】:

  • 哇,感谢您提供了很多很棒的信息!我刚到办公室,需要一些东西来消耗它,但会报告事情的进展情况和最终的解决方案。
  • 我已经在使用 django-debug-toolbar,但是应用 iterator(),我能够减少我的查询。我当然需要不断调整以将我的查询降到最低,但这让我朝着正确的方向前进;感谢您的详细回复...这使我研究了我不知道的其他领域。
【解决方案2】:

至于问题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')

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

希望对你有帮助。

【讨论】:

    【解决方案3】:

    没有直接解决问题,但我认为您应该考虑另一种方法。

    根据我的理解:

    • 视图可能会经常被请求。
    • 数据应该很少更改。
    • 需要复杂的数据操作(按年、月、日等对字段求和)

    无需在每次有人请求视图时都执行相同的查询。

    一步加载所有数据并在视图内执行操作。您可以使用像 Pandas 这样的库并创建复杂的数据集。该视图现在将受 CPU 限制,因此请使用像 Redis 这样的缓存系统来避免重新计算。当数据发生变化时失效。

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

    【讨论】:

      猜你喜欢
      • 2011-09-24
      • 2020-06-02
      • 2014-07-31
      • 2017-06-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多