Django:queryset.count()在链式过滤器上比单个过滤器慢得多,无论返回的查询大小如何 - 是否有解决方案?

时间:2017-07-07 17:41:28

标签: python mysql django performance count

编辑:谢谢哈坎的最佳解决方案 -

queriedForms.filter(pk__in=list(formtype.form_set.all().filter(formrecordattributevalue__record_value__contains=constraint['TVAL'], formrecordattributevalue__record_attribute_type__pk=rtypePK).values_list('pk', flat=True))).count()

我尝试了更多他的建议,但我无法避免内部加入 - 这似乎是一个稳定的解决方案,确实让我变小,但可预测的速度全面增加。仔细阅读他的答案!

我一直在努力解决一个我没有看到网上答案的问题。

在Django中链接两个过滤器时,例如

masterQuery = bigmodel.relatedmodel_set.all()
masterQuery = masterQuery.filter(name__contains="test")
masterQuery.count() 
#returns 100,000 results in < 1 second
#test filter--all 100,000+ names have "test x" where x is 0-9 
storedCount = masterQuery.filter(name__contains="9").count()
#returns ~50,000 results but takes 5-6 seconds

尝试稍微不同的方式:

masterQuery = masterQuery.filter(name__contains="9")
masterQuery.count()
#also returns ~50,000 results in 5-6 seconds

表演&amp;合并似乎有点提高性能,例如

masterQuery = bigmodel.relatedmodel_set.all()
masterQuery = masterQuery.filter(name__contains="test") 
(masterQuery & masterQuery.filter(name__contains="9")).count()

似乎count在查询集中的单个过滤器之外的时间要长得多。

我认为它可能与mySQL有关,显然它不喜欢嵌套语句 - 我假设两个过滤器正在创建一个嵌套查询,这会减慢mySQL的速度,无论SELECT COUNT(*)django使用什么

所以我的问题是:无论如何还要加快速度吗?我正准备做大量的常规嵌套查询,只使用查询集计数(我不需要实际的模型值),没有数据库命中来加载模型。例如我不需要从数据库加载100,000个模型,我只需要知道那里有100,000个。通过查询集而不是len()显然要快得多,但即使在5秒内,当我为整个复杂查询运行40个计数时计数是3分钟 - 我希望它不到一分钟。我是幻想还是有人建议如何在提高服务器处理器速度之外完成这项工作?

编辑:如果它有用 - 链接过滤器的time.clock()速度为.3秒()计数 - 控制台和django视图输出的实际时间是5-6秒

EDIT2:要回答有关索引的任何问题,过滤器会为链中的每个链接使用索引值和非索引值:

mainQuery = masterQuery = bigmodel.relatedmodel_set.all()
mainQuery = mainQuery.filter(reverseforeignkeytestmodel__record_value__contains="test", reverseforeignkeytestmodel__record_attribute_type__pk=1)
#Where "record_attribute_type" is another foreign key being used as a filter
mainQuery.count() #produces 100,000 results in < 1sec
mainQuery.filter(reverseforeignkeytestmodel__record_value__contains="9", reverseforeignkeytestmodel__record_attribute_type__pk=5).count()
#produces ~50,000 results in 5-6 secs

因此,链中的每个过滤器在功能上都相似,它是一个AND过滤器(条件,条件),其中一个条件被索引,而另一个不是。我不能索引这两个条件。

编辑3: 类似的查询导致较小的结果,例如&LT;无论嵌套如何,10,000都快得多 - 例如。链中的第一个过滤器在〜<1秒内产生10,000个结果,但链中的第二个过滤器将在〜<1秒内产生5,000个结果

编辑4: 仍然没有根据@Hakan的解决方案工作

mainQuery = bigmodel.relatedmodel_set.all()
#Setup the first filter as normal
mainQuery = mainQuery.filter(reverseforeignkeytestmodel__record_value__contains="test", reverseforeignkeytestmodel__record_attribute_type__pk=1)

#Grab a values list for the second chained filter instead of chaining it    
values = bigmodel.relatedmodel_set.all().filter(reverseforeignkeytestmodel__record_value__contains="test", reverseforeignkeytestmodel__record_attribute_type__pk=8).values_list('pk', flat=True)
#filter the first query based on the values_list rather than a second filter
mainQuery = mainQuery.filter(pk__in=values)
mainQuery.count()
#Still takes on average the same amount of time after enough test runs--seems to be slightly faster than average--similar to the (quersetA & querysetB) merge solution I tried.

我可能做错了 - 但计数结果在新的value_list过滤技术之间是一致的,例如我得到了相同的结果#。所以它肯定有效 - 但似乎花了相同的时间

编辑5: 同样基于@Hakan的解决方案,稍作调整

mainQuery.filter(pk__in=list(formtype.form_set.all().filter(formrecordattributevalue__record_value__contains=constraint['TVAL'], formrecordattributevalue__record_attribute_type__pk=rtypePK).values_list('pk', flat=True))).count()

对于查询集中的较大结果,这似乎运行得更快,例如&GT; 50,000,但实际上在较小的查询集结果上要慢得多,例如&LT; 50,000 - 它们曾经是<1秒 - 有时2-3秒在1秒内运行以进行链式过滤,它们现在都需要1秒钟。本质上,较大查询集中的速度增益已被较小查询集中的速度损失无效。

我仍然会按照他的建议进一步尝试分解查询 - 但我不确定我是否能够。我会再次更新(可能在星期一),当我想出来并让每个人都知道进展时。

1 个答案:

答案 0 :(得分:1)

不确定这是否有帮助,因为我没有要测试的mysql项目。

QuerySet API reference包含有关嵌套查询性能的部分。

  

效果考虑因素

     

谨慎使用嵌套查询并了解您的数据库   服务器的性能特征(如果有疑问,基准测试!)。一些   数据库后端,尤其是MySQL,不优化嵌套查询   很好。在这些情况下,提取列表更有效   值,然后将其传递给第二个查询。也就是说,执行两个   查询而不是一个:

values = Blog.objects.filter(
    name__contains='Cheddar').values_list('pk', flat=True) 
entries = Entry.objects.filter(blog__in=list(values)) 
     

注意在Blog QuerySet周围调用list()来强制执行第一个查询。   没有它,将执行嵌套查询,因为QuerySets是   懒惰。

所以,也许你可以通过尝试这样的方式来提高性能:

masterQuery = bigmodel.relatedmodel_set.all()
pks = list(masterQuery.filter(name__contains="test").values_list('pk', flat=True))
count = masterQuery.filter(pk__in=pks, name__contains="9")

由于您的初始MySQL性能太慢,因此在Python中而不是在数据库中执行第二步甚至可能更快。

names = masterQuery.filter(name__contains='test').values_list('name')
count = sum('9' in n for n in names)

编辑: 从您的更新中,我看到您正在查询相关模型中的字段,这会导致多个sql JOIN操作。这可能是查询速度缓慢的一个重要原因。

为了避免加入,您可以尝试这样的事情。目标是避免跨关系进行深度链接查找。

# query only RelatedModel, avoid JOIN
related_pks = RelatedModel.objects.filter(
     record_value__contains=constraint['TVAL'],
     record_attribute_type=rtypePK,
).values_list('pk', flat=True)

# list(queryset) will do a database query, resulting in a list of integers.
pks_list = list(related_pks)

# use that result to filter your main model. 
count = MainModel.objects.filter(
     formrecordattributevalue__in=pks_list
).count()

我假设关系被定义为从MainModel到RelatedModel的外键。