Trong Django, cho rằng tôi có một QuerySet
mà tôi sẽ lặp lại và in kết quả của nó, tùy chọn tốt nhất để đếm các đối tượng là gì? len(qs)
hoặc qs.count()
?
(Ngoài ra, việc đếm các đối tượng trong cùng một lần lặp không phải là một tùy chọn.)
Trong Django, cho rằng tôi có một QuerySet
mà tôi sẽ lặp lại và in kết quả của nó, tùy chọn tốt nhất để đếm các đối tượng là gì? len(qs)
hoặc qs.count()
?
(Ngoài ra, việc đếm các đối tượng trong cùng một lần lặp không phải là một tùy chọn.)
Câu trả lời:
Mặc dù tài liệu Django khuyên bạn nên sử dụng count
hơn là len
:
Lưu ý: Không sử dụng
len()
trên QuerySets nếu tất cả những gì bạn muốn làm là xác định số lượng bản ghi trong tập hợp. Sẽ hiệu quả hơn nhiều khi xử lý số lượng ở cấp cơ sở dữ liệu, bằng cách sử dụng SQLSELECT COUNT(*)
và Django cung cấp mộtcount()
phương pháp cho chính xác lý do này.
Vì bạn vẫn đang lặp lại QuerySet này, nên kết quả sẽ được lưu vào bộ nhớ đệm (trừ khi bạn đang sử dụng iterator
), và do đó, nó sẽ được ưu tiên sử dụng hơn len
, vì điều này tránh đánh lại cơ sở dữ liệu và cũng có thể truy xuất một số kết quả khác !) .
Nếu bạn đang sử dụng iterator
, thì tôi khuyên bạn nên bao gồm một biến đếm khi bạn lặp lại (thay vì sử dụng số đếm) vì những lý do tương tự.
Lựa chọn giữa len()
vàcount()
tùy thuộc vào tình huống và cần hiểu sâu sắc cách chúng hoạt động để sử dụng chúng một cách chính xác.
Hãy để tôi cung cấp cho bạn một vài tình huống:
(quan trọng nhất) Khi bạn chỉ muốn biết số lượng phần tử và bạn không có kế hoạch xử lý chúng theo bất kỳ cách nào, điều quan trọng là phải sử dụng count()
:
NÊN: queryset.count()
- điều này sẽ thực hiện đơnSELECT COUNT(*) some_table
truy vấn , tất cả tính toán được thực hiện ở phía RDBMS, Python chỉ cần truy xuất số kết quả với chi phí cố định là O (1)
KHÔNG NÊN: len(queryset)
- điều này sẽ thực hiện SELECT * FROM some_table
truy vấn, tìm nạp toàn bộ bảng O (N) và yêu cầu bộ nhớ O (N) bổ sung để lưu trữ nó. Đây là điều tồi tệ nhất có thể được thực hiện
Dù sao thì khi bạn định tìm nạp bộ len()
truy vấn count()
thì tốt hơn một chút là sử dụng cách này sẽ không gây ra truy vấn cơ sở dữ liệu bổ sung như sau:
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
Đếm:
queryset.count() # this will perform an extra db query - len() did not
for obj in queryset: # fetching data
pass
Trường hợp thứ 2 được hoàn nguyên (khi bộ truy vấn đã được tìm nạp):
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)
Mọi thứ sẽ rõ ràng khi bạn xem qua "phần dưới":
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)
Tham khảo tốt trong tài liệu Django:
QuerySet
triển khai theo ngữ cảnh.
Tôi nghĩ việc sử dụng len(qs)
có ý nghĩa hơn ở đây vì bạn cần phải lặp lại các kết quả. qs.count()
là một lựa chọn tốt hơn nếu tất cả những gì bạn muốn thực hiện in ra số lượng và không lặp lại kết quả.
len(qs)
sẽ nhấn vào cơ sở dữ liệu select * from table
trong khi qs.count()
sẽ nhấn db với select count(*) from table
.
cũng qs.count()
sẽ trả về số nguyên và bạn không thể lặp lại nó
Đối với những người thích các phép đo thử nghiệm (Postresql):
Nếu chúng ta có một mô hình Người đơn giản và 1000 trường hợp của nó:
class Person(models.Model):
name = models.CharField(max_length=100)
age = models.SmallIntegerField()
def __str__(self):
return self.name
Trong trường hợp trung bình, nó cho:
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)
Vậy làm thế nào bạn có thể thấy count()
gần như 2x nhanh hơn len()
trong trường hợp thử nghiệm đặc biệt này.
Tóm tắt những gì người khác đã trả lời:
len()
sẽ tìm nạp tất cả các bản ghi và lặp lại chúng.count()
sẽ thực hiện thao tác SQL COUNT (nhanh hơn nhiều khi xử lý bộ truy vấn lớn).Cũng đúng là nếu sau thao tác này, toàn bộ tập truy vấn sẽ được lặp lại, thì về tổng thể, nó có thể hiệu quả hơn một chút để sử dụng len()
.
Tuy nhiên
Trong một số trường hợp, chẳng hạn như khi có giới hạn về bộ nhớ, có thể thuận tiện (khi có thể) để phân chia hoạt động được thực hiện trên các bản ghi. Điều đó có thể đạt được bằng cách sử dụng phân trang django .
Sau đó, sử dụng count()
sẽ là sự lựa chọn và bạn có thể tránh phải tìm nạp toàn bộ bộ truy vấn cùng một lúc.