Django 查询集上的 Count vs len

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

(另外,在同一个迭代中计算对象不是一个选项。)

101189 次浏览

尽管 Django docs推荐使用 count而不是 len:

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

因为无论如何都要迭代这个 QuerySet,结果将被缓存(除非使用 iterator) ,所以最好使用 len,因为 this avoids hitting the database again, and also the possibly of retrieving a different number of results!).
如果您使用的是 iterator,那么出于同样的原因,我建议在迭代时包含一个计数变量(而不是使用 count)。

我认为在这里使用 len(qs)更有意义,因为您需要对结果进行迭代。如果您希望打印计数而不是迭代结果,那么 qs.count()是一个更好的选择。

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

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

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

让我给你们提供一些情景:

  1. (最重要的)当你只想知道元素的数量而不打算以任何方式处理它们时,使用 count()是至关重要的:

DO: queryset.count() - this will perform single SELECT COUNT(*) FROM some_table query, all computation is carried on RDBMS side, Python just needs to retrieve the result number with fixed cost of O(1)

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

  1. 无论如何,当您打算获取查询集时,最好使用 len(),它不会像 count()那样导致额外的数据库查询

len()(一个分贝查询)

    len(queryset) # SELECT * 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

count()(两个 db 查询!) :

    queryset.count() # First db query SELECT COUNT(*)


for obj in queryset: # Second db query (fetching data) SELECT *
pass
  1. 恢复的第2种情况(当查询集已经获取时) :

     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)
    

只要你看一眼“引擎盖下面”,一切都会变得清晰起来:

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 文档中的好参考资料:

对于喜欢测试测量的人(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()2倍

Summarizing what others have already answered:

  • len()将获取所有记录并对它们进行迭代。
  • count()将执行 SQLCOUNT 操作(处理大型查询集时要快得多)。

如果在这个操作之后,整个查询集将被迭代,那么作为一个整体,使用 len()的效率可能会稍微高一些。

然而

在某些情况下,例如当存在内存限制时,可以方便地(在可能的情况下)分割对记录执行的操作。 这可以通过使用 Django 分页来实现。

然后,可以选择使用 count(),这样就可以避免同时获取整个查询集。