django中big queryset/context的优化

Optimization of big queryset/context in django

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

型号:

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 table 的 TM 段和零个或多个来自 MachineTransTable table.

的 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

一切正常,但:

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

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(): 因为它仍然是 return 一个空的可迭代对象。

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

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

根据 serg 的建议,我开始深入研究问题,最后我能够预取所有需要的信息。我想使用彻底的 table 改变预取的工作方式......这是正确的查询集:

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 个...