减少Django数据库查询

Stu*_*ter 7 python django django-templates django-views python-3.x

我有非常大的数据集和增长,我需要创建许多过滤器,但它会很快失控,并希望有人可以帮助我将一些查询组合成一个单独的调用.以下是我的观点的开始.

调用#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')
Run Code Online (Sandbox Code Playgroud)

调用#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'))
Run Code Online (Sandbox Code Playgroud)

问题是,我需要创建几十个过滤器,因为我有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
Run Code Online (Sandbox Code Playgroud)

Par*_*us- 14

有许多方法可以使用Django ORM优化数据库查询.像往常一样,Django文档很棒,并且有很好的列表.以下是查询优化的一些快速提示:

1) iterator()

如果您只访问queryset一次.所以例如你可以用它,

traffic = Traffic.objects.all()

for t in traffic.iterator():
    ...
    ...
Run Code Online (Sandbox Code Playgroud)

2) db_index=True

在定义你的领域时models.正如Django文档所说,

在确定应该添加哪些索引之后,这是第一优先级.使用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)
Run Code Online (Sandbox Code Playgroud)

3) prefetch_related()select_related()

如果您有自己的关系models,使用prefetch_relatedselect_related将是一个选择.根据Django文档,

select_related通过SQL join在SELECT语句中创建和包含相关对象的字段来工作.因此,select_related在相同的数据库查询中获取相关对象.但是,为了避免因加入"多"关系而产生的更大的结果集,select_related仅限于单值关系 - 外键和一对一.

prefetch_related另一方面,对每个关系进行单独查找,并在Python中进行"连接".这允许它预取多对多和多对一对象,select_related除了支持的外键和一对一关系之外,这些对象无法使用 select_related.

select_related做一个join,prefetch_related做两个单独的查询.使用这些可以使您的查询速度提高30%.


4) Django分页

如果您的template设计允许您在多个页面中显示结果,则可以使用Pagination.


5) 查询集是懒惰的

您还需要了解Django Querysets是惰性的,这意味着它不会查询数据库,直到它被使用/评估.Django中的查询集表示数据库中的多个行,可选地由查询过滤.例如,

traffic = Traffic.objects.all()
Run Code Online (Sandbox Code Playgroud)

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

for t in traffic.iterator():
    print(t.sessions)
Run Code Online (Sandbox Code Playgroud)

6) django-debug-toolbar

Django Debug Toolbar是一组可配置的面板,显示有关当前请求/响应的各种调试信息,点击后,显示有关面板内容的更多详细信息.这包括:

  • 请求计时器
  • SQL查询包括执行时间和EXPLAIN每个查询的链接

修改你的代码 :(记住Querysets是懒惰的)

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'))
...
...
Run Code Online (Sandbox Code Playgroud)

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

{% for t in traffic.iterator %}
    {{ t.sessions }}
    ...
    ...
{% endfor %}
Run Code Online (Sandbox Code Playgroud)