【问题标题】:Count vs len on a Django QuerySetDjango QuerySet 上的计数与 len
【发布时间】:2012-12-28 22:04:51
【问题描述】:

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

(同时考虑到在同一迭代中计算对象不是一种选择。)

【问题讨论】:

  • 有趣的问题。我建议对此进行分析。我会很感兴趣!我对 python 的了解不够,无法知道完全评估的对象上的 len() 是否有很多开销。它可能比计数更快!

标签: python django performance


【解决方案1】:

len()count() 之间进行选择取决于具体情况,值得深入了解它们的工作原理以正确使用它们。

让我给你提供几个场景:

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

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

不要: 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()(两个数据库查询!):

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

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

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

【讨论】:

  • 出色的答案,+1 用于在上下文中发布 QuerySet 实现。
  • 确实是完美的答案。解释使用什么,更重要的是,解释使用的为什么
【解决方案2】:

总结其他人已经回答的内容:

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

同样,如果在此操作之后,整个查询集将被迭代,那么作为整体使用len() 可能会更有效。

但是

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      虽然Django docs 推荐使用count 而不是len

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

      由于您无论如何都在迭代这个 QuerySet,the result will be cached(除非您使用的是 iterator),所以最好使用 len,因为 这样可以避免再次访问数据库,而且可能检索到不同数量的结果!)。
      如果您使用的是iterator,那么出于同样的原因,我建议您在迭代时包含一个计数变量(而不是使用计数)。

      【讨论】:

        【解决方案5】:

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

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

        qs.count() 也将返回整数,您不能对其进行迭代

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-03-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-02
          • 2021-02-28
          相关资源
          最近更新 更多