QuerySet
,那么计算对象数量的最佳选项是什么? len(qs)
还是 qs.count()
?(也假设在相同迭代中计算对象数量不是一种选择。)
QuerySet
,那么计算对象数量的最佳选项是什么? len(qs)
还是 qs.count()
?len()
和count()
之间,取决于具体情况。深入理解它们的工作原理,才能正确地使用它们。
我给你提供一些场景:
count()
非常重要:DO: queryset.count()
- 这将执行单个SELECT COUNT(*) FROM some_table
查询,所有计算都在RDBMS端执行,Python只需要检索结果数字,固定成本为O(1)
DON'T: len(queryset)
- 这将执行SELECT * FROM some_table
查询,获取整个表O(N),并且需要额外的O(N)内存来存储它。这是最差的情况
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
Reverted 2nd case (when queryset has already been fetched):
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文档中有很好的参考资料:
我认为在这里使用 len(qs)
更有意义,因为您需要遍历结果。qs.count()
是一个更好的选择,如果您只想打印计数,而不是遍历结果。
len(qs)
将使用 select * from table
命令对数据库进行查询,而 qs.count()
则会使用 select count(*) from table
命令。
此外,qs.count()
将返回整数,您无法对其进行迭代。
针对喜欢测试测量(Postgresql)的人:
如果我们有一个简单的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倍。
总结其他人已经回答的内容:
len()
将获取所有记录并对它们进行迭代。count()
将执行 SQL COUNT 操作(处理大型查询集时速度更快)。另外,如果在这个操作后,整个查询集将被迭代,那么作为整体来说使用 len()
可能会稍微更有效率。
然而
在某些情况下,例如具有内存限制时,将操作分割成多次可以更加方便(如果可能的话)。 这可以通过使用Django分页来实现。
然后,使用 count()
就是选择,你可以避免一次性获取整个查询集。
我进行了一个实验,测试了使用count()和len()函数在10百万行数据中查询速度的差异。*我使用了PostgreSQL数据库。
count() | len() | |
---|---|---|
模型查询 | 1.02秒 | 46.13秒 |
原始查询 | 0.48秒 | 3.16秒 |
因此,速度最快的顺序如下:
count()
与原始查询 (0.48秒)count()
与模型查询 (1.02秒)len()
与原始查询 (3.16秒)len()
与模型查询 (46.13秒)我建议基本上使用count()
与模型查询,因为它比len()
更快,代码更少,更方便,而且比原始查询更方便。但是,当使用select_for_update()时,您应该使用len()
与模型查询,因为select_for_update()
与count()
不起作用,而且比原始查询更方便。
首先,我创建了只有id
列的Test
模型:
# "store/models.py"
from django.db import models
class Test(models.Model):
pass
然后,运行以下命令:
python manage.py makemigrations && python manage.py migrate
然后,使用psql一次性向store_test
表插入了1000万行:
postgres=# INSERT INTO store_test (id) SELECT generate_series(1, 10000000);
INSERT 0 10000000
Time: 29929.337 ms (00:29.929)
test_view()
函数:# "store/views.py"
from time import time
from .models import Test
from django.db import connection
from django.http import HttpResponse
def test_view(request):
# "count()" with model query
start = time()
print(Test.objects.all().count(), "- count() - Model query")
end = time()
print(round(end - start, 2), "seconds\n")
# "len()" with model query
start = time()
print(len(Test.objects.all()), "- len() - Model query")
end = time()
print(round(end - start, 2), "seconds\n")
# "count()" with raw query
start = time()
with connection.cursor() as cursor:
cursor.execute("SELECT count(*) FROM store_test;")
print(cursor.fetchone()[0], "- count() - Raw query")
end = time()
print(round(end - start, 2), "seconds\n")
# "len()" with raw query
start = time()
with connection.cursor() as cursor:
cursor.execute("SELECT * FROM store_test;")
print(len(cursor.fetchall()), "- len() - Raw query")
end = time()
print(round(end - start, 2), "seconds\n")
return HttpResponse("Test_view")
在控制台上输出:
10000000 - count() - Model query
1.02 seconds
10000000 - len() - Model query
46.13 seconds
10000000 - count() - Raw query
0.48 seconds
10000000 - len() - Raw query
3.16 seconds
[18/Dec/2022 07:12:14] "GET /store/test_view/ HTTP/1.1" 200 9