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
一切正常,但:
- 我每次调用 aSeg.segmentsTM.all() 和 aSeg.segmentsMT.all() 时都会访问数据库,因为我猜预取并没有阻止它...这导致数百个重复查询
- 每次我从分页加载更多条目时都会重复所有这些查询(换句话说......每次由于滚动而呈现更多条目时,都会请求完整的条目集......我也尝试使用lazy_paginate 但没有任何变化)
- 原则上,我在 get_context_data 中的所有逻辑(还有更多,但我只是提供了基本代码)都可以在仅传递查询集的模板中重现......或者由客户端重现jquery/javascript 代码,但我不认为这样进行是个好主意...
所以我的问题是...我可以优化此代码以减少数据库命中次数和生成响应的时间吗?只是为了让您了解一个相对较小的文件(在 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 个...
我必须展示一个非常复杂的页面,其中包含大量数据,这些数据来自 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
一切正常,但:
- 我每次调用 aSeg.segmentsTM.all() 和 aSeg.segmentsMT.all() 时都会访问数据库,因为我猜预取并没有阻止它...这导致数百个重复查询
- 每次我从分页加载更多条目时都会重复所有这些查询(换句话说......每次由于滚动而呈现更多条目时,都会请求完整的条目集......我也尝试使用lazy_paginate 但没有任何变化)
- 原则上,我在 get_context_data 中的所有逻辑(还有更多,但我只是提供了基本代码)都可以在仅传递查询集的模板中重现......或者由客户端重现jquery/javascript 代码,但我不认为这样进行是个好主意...
所以我的问题是...我可以优化此代码以减少数据库命中次数和生成响应的时间吗?只是为了让您了解一个相对较小的文件(在 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 个...