count, size, length trong Rails: khi nào query DB, khi nào load records
Trong Ruby on Rails (ActiveRecord), count, size, length đều trả về một con số, nhưng cách để ra được con số đó rất khác nhau.
Giả sử:
users = User.where(active: true)
1. count: luôn đếm trực tiếp trên DB (COUNT *)
count : gửi một câu SELECT COUNT(*) ... xuống DB và trả về kết quả.
irb(main)> users.count
User Count (1.0ms) SELECT COUNT(*) FROM users WHERE active = true
=> 1001
Điều gì xảy ra bên dưới:
-
Rails sinh ra SQL dạng:
SELECT COUNT(*) FROM users WHERE active = true; -
DB chỉ tính toán số lượng dòng thỏa điều kiện và trả về một con số duy nhất (ví dụ
1001). -
Rails nhận con số đó và trả về cho bạn một
Integer.
2. length: nếu chưa load thì load hết vào RAM rồi mới đếm
Với ActiveRecord::Relation, length không đếm trực tiếp trên DB, mà luôn đếm trên mảng:
irb(main)> users.length
=> 1001
Case 1: users chưa được load
Khi bạn chưa làm gì với users ngoài User.where(...), bên trong Rails sẽ làm gần như:
-
Chạy query:
SELECT * FROM users WHERE active = true; -
DB trả về toàn bộ các dòng (ví dụ 1 triệu dòng).
-
Rails tạo ra 1 triệu object
Usertrong RAM và gom lại thành một mảng. -
Sau đó mới gọi
Array#lengthtrên mảng đó để đếm số phần tử.
Case 2: users đã được load từ trước
Nếu trước đó bạn đã làm gì đó khiến users load rồi, ví dụ:
users = User.where(active: true)
users.to_a
thì lúc này:
- dữ liệu đã nằm sẵn trong RAM dưới dạng một mảng object
User - gọi
users.lengthchỉ đơn giản là gọiArray#lengthtrên mảng đó - không chạy thêm query nào nữa
3. size: linh hoạt, chưa load thì giống count, đã load thì giống length
size là method tự chọn cách làm tối ưu tùy trạng thái của relation.
irb(main)> users.size
=> 1001
Case 1: users chưa load
-
Rails thấy relation này chưa có dữ liệu trong RAM.
-
Thay vì
SELECT *, nó sinh ra câuCOUNT(*)giống nhưcount:SELECT COUNT(*) FROM users WHERE active = true;
Case 2: users đã load
users = User.where(active: true)
users.to_a
users.size # đếm trên mảng đã có
- Lúc này,
sizekhông query nữa. - Nó chỉ lấy số phần tử từ mảng đã có trong RAM (giống
length).
4. Câu hỏi phỏng vấn Rails kinh điển
User.all.length
Nếu bảng users có 1 triệu record, chuyện gì đang xảy ra?
User.alltạo ra mộtActiveRecord::Relation..lengthtrên relation chưa load sẽ:-
sinh query:
SELECT * FROM users; -
DB trả về toàn bộ 1 triệu dòng.
-
Rails tạo 1 triệu object
Uservà giữ tất cả trong RAM. -
cuối cùng mới gọi
Array#lengthđể đếm.
-
Viết lại:
-
Nếu bạn chỉ cần số lượng user:
User.count # hoặc User.where(active: true).count -
Nếu bạn có một scope và muốn đếm “an toàn”:
scope = User.where(active: true) scope.size # để Rails tự quyết định đếm trên DB hay trên mảng đã load
5. Kết luận
count:- Luôn chạy
SELECT COUNT(*) ...trên DB, không load từng record, - Phù hợp khi bạn chỉ cần con số.
- Luôn chạy
length:- Nếu relation chưa load → chạy
SELECT * ..., load toàn bộ dữ liệu vào RAM rồi mới đếm - Nếu đã load → chỉ đếm trên mảng đã có, không query thêm
- Nếu relation chưa load → chạy
size:- Nếu chưa load → đếm bằng
COUNT(*)trên DB (giốngcount) - Nếu đã load → đếm trên mảng trong RAM (giống
length)
- Nếu chưa load → đếm bằng
All rights reserved