So sánh count() và len() trong QuerySet: Tối ưu Django

Trong một lần review code gần đây, mình nhận được nhận xét yêu cầu đổi từ dùng len() sang count() để đếm số dòng (row). Vì vẫn còn là lính mới với ORM và Django, mình đã lướt vài bài trên stackoverflow và thấy mọi người đều khuyên nên dùng count để kiểm tra số lượng dòng. Vậy hãy cùng tìm hiểu lý do tại sao nhé.
Giả sử bây giờ mình muốn kiểm tra số lượng User trong dịch vụ của mình.
(Nếu hệ thống có hàng triệu hay chục triệu user mà bạn làm trò này cho vui thì cẩn thận bị mấy anh DevOps của công ty gọi lên uống trà đấy nhé.)
qs = User.objects.all()
# Có thể dùng count() của queryset.
def user_count_by_count():
return qs.count()
# Hoặc kiểm tra bằng len()
def user_count_by_len():
return len(qs)Lúc này, với count(), câu lệnh SELECT COUNT(*) FROM User sẽ được chạy trong RDBMS, và giá trị đếm sẽ được trả về cho Python.
Còn với len(), câu lệnh SELECT * FROM User sẽ được chạy một lần trong RDBMS, sau đó kết quả trả về Python mới được đếm bằng len().
Người ta thường bảo nếu cần đếm số dòng thì hãy dùng count(). Lý do là nếu bạn dùng len() để thay thế, quá trình fetch dữ liệu sẽ mất thời gian O(N). Ngoài ra, khi lưu kết quả này vào bộ nhớ của web server, dung lượng lưu trữ sẽ tốn O(N), và thời gian sao chép dữ liệu cũng khiến thời gian xử lý tăng thêm O(N). Thêm vào đó là thời gian chạy hàm len() nữa. Dù hai quá trình này phụ thuộc vào hạ tầng, nhưng người ta nói rằng count() thường nhanh hơn khoảng 2 lần.
Tuy nhiên, cũng có lúc dùng len() lại có lợi hơn count().
qs = User.objects.all()
def prefer_len_to_count():
# Giả sử trong thông tin User có trường telephone và ta gộp thông tin đó thành một list.
telephones = [p.telephone for p in qs]
# Làm gì đó với thông tin này...
...
# Lấy số lượng của qs (dùng để làm gì thì tùy bạn nghĩ nhé)
len(qs)Trong tình huống bạn đã fetch queryset ra để sử dụng như trên thì len() lại có lợi hơn một chút xíu. Lý do là vì nếu dùng count(), bạn sẽ phải truy cập vào RDBMS tới hai lần (một lần để fetch, một lần để count()).
Nói câu này có thể bị mấy anh DevOps mắng, nhưng nếu không phải đếm một lượng dòng khổng lồ (tiêu chuẩn "khổng lồ" này còn tùy thuộc vào hạ tầng) thì cứ dùng count() cũng chẳng sao cả.
Tất nhiên, trong template thì việc dùng count() cũng được khuyến khích hơn.
Đoạn code dùng count dưới đây
{{ some_queryset.count }}được khuyên dùng hơn là dùng len như bên dưới.
{{ len(some_queryset) }}Tham khảo: stackoverflow.com/questions/14327036/count-vs-len-on-a-django-queryset