我必须呈现一个非常复杂的页面,其中有大量数据来自与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代码,但我不认为这是一个好主意继续这样…
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…