【问题标题】:Optimization of big queryset/context in djangodjango 中大查询集/上下文的优化
【发布时间】:2016-10-08 14:08:49
【问题描述】:

我必须呈现一个非常复杂的页面,其中包含来自与 ForeignKey 和 ManyToManyField 相关的 3 个不同表的大量数据...我能够做我想做的事,但性能很糟糕,我一直在努力寻找更好的方法...这里有详细的代码:

型号:

class CATSegmentCollection(models.Model):
    theFile = models.ForeignKey('app_file.File', related_name='original_file')
    segmentsMT = models.ManyToManyField('app_mt.MachineTransTable', related_name='segMT', blank=True,)
    segmentsTM = models.ManyToManyField('app_tm.TMTable',           related_name='segTM', blank=True, through='app_cat.TM_Source_quality',)
    ...

class TM_Source_quality(models.Model):
    catSeg = models.ForeignKey('app_cat.CATSegmentCollection')
    tmSeg = models.ForeignKey('app_tm.TMTable')
    quality = models.IntegerField()

class MachineTransTable(models.Model):
    mt = models.ForeignKey('app_mt.MT_available', blank=True, null=True, )
    ...

class TMTable(models.Model):
    ...

从这些模型中(我刚刚写了与我的问题相关的内容),我展示了与单个文件相关的所有 CATSegmentCollection 条目......连同其相关的 TM 和 MT 段。换句话说,CATSegmentCollection 中的每个条目都有 TMTable 表中的零个或多个 TM 段和 MachineTransTable 表中的零个或多个 MT 段。

这就是我在 ListView 中所做的(我使用 AjaxListView,因为我使用的是来自 django-el-pagination 的无限滚动分页):

class CatListView(LoginRequiredMixin, AjaxListView):
    Model = CATSegmentCollection
    template_name = 'app_cat/cat.html'
    page_template='app_cat/cat_page.html'

    def get_object(self, queryset=None):
        obj = File.objects.get(id=self.kwargs['file_id'])
        return obj

    def get_queryset(self):
        theFile = self.get_object()
        return CATSegmentCollection.objects.filter(theFile=theFile).prefetch_related('segmentsMT').prefetch_related('segmentsTM').order_by('segment_order')

    def get_context_data(self, **kwargs):
        context = super(CatListView, self).get_context_data(**kwargs)
        contextSegment = []
        myCatCollection = self.get_queryset()
        theFile = self.get_object()
        context['file'] = theFile
        for aSeg in myCatCollection:
            contextTarget = []
            if aSeg.segmentsTM.all():
                for aTargetTM in aSeg.tm_source_quality_set.all():
                    percent_quality = ...
                    contextTarget.append( {
                        "source"        : aTargetTM.tmSeg.source,
                        "target"        : aTargetTM.tmSeg.target,
                        "quality"       : str(percent_quality) + '%',
                        "origin"        : "TM",
                        "orig_name"     : aTargetTM.tmSeg.tm_client.name,
                        "table_id"      : aTargetTM.tmSeg.id,
                    })
            if aSeg.segmentsMT.all():
                for aTargetMT in aSeg.segmentsMT.all():
                    contextTarget.append( {
                        "target"    : aTargetMT.target,
                        "quality"   : "",
                        "origin"    : "MT",
                        "orig_name" : aTargetMT.mt.name,
                        "table_id"  : aTargetMT.id
                    })
            contextSegment.append( {
                "id"            : aSeg.id,
                "order"         : aSeg.segment_order,
                "source"        : aSeg.source,
                "target"        : contextTarget,
            })
        context['segments'] = contextSegment
        return context

一切正常,但是:

  • 我每次调用 aSeg.segmentsTM.all() 和 aSeg.segmentsMT.all() 时都会访问数据库,因为我猜预取不会阻止它...这会导致数百个重复查询
  • 每次我从分页加载更多条目时,所有这些查询都会重复(换句话说......每次由于滚动而呈现更多条目时,都会请求完整的条目集......我也尝试使用lazy_paginate但是没有任何变化)
  • 原则上,我在 get_context_data 中的所有逻辑(还有更多,但我只是介绍了基本代码)可以在模板中复制,只传递查询集...或者由客户端使用大量 jquery/javascript 代码但我不认为这样进行是个好主意......

所以我的问题是...我可以优化此代码以减少 DB 命中数和产生响应的时间吗?只是为了让您了解一个相对较小的文件(CATSegmentCollection 中有 300 个条目)在 6.5 秒内加载,330 个查询(超过 300 个重复)需要 0.4 秒。 DJDT时间分析给出了

domainLookup    273 (+0)
connect         273 (+0)
request         275 (+-1475922263356)
response        9217 (+-1475922272298)
domLoading      9225 (+-1475922272306)

有什么建议吗? 谢谢

【问题讨论】:

  • 有什么方法可以只获取一次数据然后在 python 中处理链接?由于您需要对其进行迭代的逻辑,一些复杂的查询无法预取。

标签: django django-orm manytomanyfield django-pagination


【解决方案1】:

优化查询数量是一个非常棘手的问题,因为要确定哪个确切的代码触发了额外的查询并不明显。所以我建议将 for 循环中的所有代码注释掉,并开始逐行取消注释,同时监控哪一行会导致额外的查询,并逐步优化它。

一些观察:

  • 你需要仔细声明你在prefetch_related内部接触到的所有深层关系,比如:

    .prefetch_related('segmentsTM', 'segmentsTM__tm_source_quality_set', 'segmentsTM__tm_source_quality_set__tmSeg', 'segmentsTM__tm_source_quality_set__tmSeg__tm_client', 'segmentsMT', 'segmentsMT__mt')
    
  • 在循环之前无需检查if aSeg.segmentsMT.all():,因为它仍然会返回一个空的可迭代对象。

  • related_name='segMT' 无关的注释在您的CATSegmentCollection 模型中。 related_name 字段用于声明应如何从关系的另一端访问当前模型,因此您可能希望两个字段都使用类似 related_name='cATSegmentCollections' 的内容

最后,您应该能够将其优化到大约 10 个查询(每个关系大约一个)。成功标准是没有大量的WHERE foreign_id=X 查询并且只有WHERE foreign_id IN (X,Y,...) 类型的查询。

【讨论】:

  • 谢谢...我正在使用您的建议,但我不确定如何处理直通表...预取“segmentsMT”和“segmentsMT__mt”解决了第二个问题环形。对于第一个循环(有一个直通表),问题是不同的。命令“for aTargetTM in aSeg.tm_source_quality_set.all():”总是触发数百个查询。我正在使用“segmentsTM”、“segmentsTM__tm_source_quality_set”、“segmentsTM__tm_source_quality_set__catSeg”、“segmentsTM__tm_source_quality_set__tmSeg”进行预取,但这还不够……
  • 经过多次测试,我能够得到我需要的东西......关键是尝试使用 Prefetch 命令......我认为值得添加一个答案......我没有'找不到任何例子。
  • @Attilio 您可以将您的解决方案作为单独的答案发布并接受它。
【解决方案2】:

按照 serg 的建议,我开始深入研究问题,最后我能够预取所有需要的信息。我猜想使用一个完整的表会改变预取的工作方式......这是正确的查询集:

all_cat_seg = CATSegmentCollection.objects.filter(theFile=theFile).order_by('segment_order')
all_tm_source_quality_entries = TM_Source_quality.objects.filter(catSeg__in=all_cat_seg).select_related('tmSeg','tmSeg__tm_client')
prefetch = Prefetch('tm_source_quality_set',queryset=all_tm_source_quality_entries)
CATSegmentCollection.objects.filter(theFile=theFile).prefetch_related(
    prefetch,
    'segmentsMT',
    'segmentsMT__mt'
).order_by('segment_order')

使用这个查询集,我能够将查询数量减少到 10 个......

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-22
    • 2018-11-08
    • 2019-07-21
    • 2018-03-07
    • 1970-01-01
    • 1970-01-01
    • 2017-08-09
    相关资源
    最近更新 更多