django中大型queryset/context的优化



我必须呈现一个非常复杂的页面,其中有大量数据来自与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中的每个条目都有0个或多个来自TMTable表的TM段和0个或多个来自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

一切正常,但:

  • 我打DB每次我调用aSeg.segmentsTM.all()和aSeg.segmentsMT.all(),因为我猜预取是不阻止它…这将导致数百个重复查询
  • 所有这些查询都是重复的,每次我加载更多的条目从分页(换句话说…每次由于滚动而呈现更多条目时,就会请求完整的条目集。我也尝试使用lazy_paginate,但没有任何变化)
  • 原则上所有的逻辑我有在get_context_data(有更多,但我只是提出了必要的代码)可以在模板中传递只是queryset复制…或由客户端与大量的jquery/javascript代码,但我不认为这是一个好主意继续这样…
所以我的问题是……我可以优化这段代码,减少DB命中的次数和产生响应的时间吗?为了给您一个概念,一个相对较小的文件(在CATSegmentCollection中有300个条目)在6.5秒内加载,330个查询(超过300个重复)需要0.4秒。
domainLookup    273 (+0)
connect         273 (+0)
request         275 (+-1475922263356)
response        9217 (+-1475922272298)
domLoading      9225 (+-1475922272306)

有什么建议吗?由于

优化查询的数量是一个相当棘手的问题,因为确定哪些代码触发了额外的查询并不明显。因此,我建议注释掉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():,因为它仍然会返回一个空的可迭代对象

  • 关于CATSegmentCollection模型中related_name='segMT'的无关说明。related_name字段用于声明如何从关系的另一端访问当前模型,因此您可能需要为
  • 两个字段设置related_name='cATSegmentCollections'

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

根据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…

相关内容

  • 没有找到相关文章

最新更新