在Django QuerySet上计算vs len

时间:2013-01-14 21:29:25

标签: python django performance

在Django中,假设我有一个QuerySet我要迭代并打印结果,那么计算对象的最佳选择是什么? len(qs)qs.count()

(同样假设在同一次迭代中计算对象不是一种选择。)

5 个答案:

答案 0 :(得分:98)

虽然Django docs建议使用count而不是len

  

注意:如果您只想确定集合中的记录数,请不要在QuerySet上使用len()。使用SQL的SELECT COUNT(*)处理数据库级别的计数效率更高,而Django正是因为这个原因提供count()方法。

由于您正在迭代此QuerySet,the result will be cached(除非您使用iterator),因此最好使用len,因为这样可以避免命中数据库再次,也可能检索不同数量的结果!)。
如果您使用的是iterator,那么我会建议您在迭代时使用计数变量(而不是使用计数),原因相同。

答案 1 :(得分:31)

len()count()之间选择取决于具体情况,深入了解他们如何正确使用它们是值得的。

让我为您提供一些方案:

  1. (最关键)如果您只想知道元素的数量并且不打算以任何方式处理它们,那么使用count()至关重要:

    DO: queryset.count() - 这将执行单SELECT COUNT(*) some_table查询,所有计算都在RDBMS端进行,Python只需要以固定成本O检索结果编号(1)

    请勿: len(queryset) - 这将执行SELECT * FROM some_table查询,获取整个表O(N)并需要额外的O(N)内存来存储它。 这是最糟糕的事情

  2. 无论如何,当您打算获取查询集时,使用len()稍微好一些,这不会导致count()的额外数据库查询:

    len(queryset) # fetching all the data - NO extra cost - data would be fetched anyway in the for loop
    
    for obj in queryset: # data is already fetched by len() - using cache
        pass
    

    计数:

    queryset.count() # this will perform an extra db query - len() did not
    
    for obj in queryset: # fetching data
        pass
    
  3. 还原第二种情况(已提取查询集时):

    for obj in queryset: # iteration fetches the data
        len(queryset) # using already cached data - O(1) no extra cost
        queryset.count() # using cache - O(1) no extra db query
    
    len(queryset) # the same O(1)
    queryset.count() # the same: no query, O(1)
    
  4. 一旦你“瞥了一眼”,一切都会很清楚:

    class QuerySet(object):
    
        def __init__(self, model=None, query=None, using=None, hints=None):
            # (...)
            self._result_cache = None
    
        def __len__(self):
            self._fetch_all()
            return len(self._result_cache)
    
        def _fetch_all(self):
            if self._result_cache is None:
                self._result_cache = list(self.iterator())
            if self._prefetch_related_lookups and not self._prefetch_done:
                self._prefetch_related_objects()
    
        def count(self):
            if self._result_cache is not None:
                return len(self._result_cache)
    
            return self.query.get_count(using=self.db)
    

    Django文档中的好引用:

答案 2 :(得分:24)

我认为使用len(qs)更有意义,因为您需要迭代结果。 qs.count()是一个更好的选择,如果你想要做的只是打印计数而不是迭代结果。

len(qs)将使用select * from table命中数据库,而qs.count()将使用select count(*) from table命中数据库。

同样qs.count()将给出返回整数,你不能迭代它

答案 3 :(得分:1)

对于更喜欢测试测量的人(Postresql):

如果我们有一个简单的Person模型及其1000个实例:

class Person(models.Model):
    name = models.CharField(max_length=100)
    age = models.SmallIntegerField()

    def __str__(self):
        return self.name

通常情况下,它给出:

In [1]: persons = Person.objects.all()

In [2]: %timeit len(persons)                                                                                                                                                          
325 ns ± 3.09 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [3]: %timeit persons.count()                                                                                                                                                       
170 ns ± 0.572 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

在这个特定的测试案例中,如何比count()len()快{strong> 2倍。

答案 4 :(得分:1)

总结其他人已经回答的问题:

  • stroke将获取所有记录并对其进行迭代。
  • gradient将执行SQL COUNT操作(处理大型查询集时要快得多)。

如果在执行此操作之后,整个查询集将被迭代,那么从整体上来说,使用len()的效率可能会更高。

但是

在某些情况下,例如,在有内存限制的情况下,可以方便地(在可能的情况下)拆分对记录执行的操作。 可以使用django pagination来实现。

然后,使用count()是一种选择,您可以避免必须一次获取整个查询集。