0

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:

  1. Rails sinh ra SQL dạng:

    SELECT COUNT(*) FROM users WHERE active = true;
    
  2. 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).

  3. 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ư:

  1. Chạy query:

    SELECT * FROM users WHERE active = true;
    
  2. DB trả về toàn bộ các dòng (ví dụ 1 triệu dòng).

  3. Rails tạo ra 1 triệu object User trong RAM và gom lại thành một mảng.

  4. Sau đó mới gọi Array#length trê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.length chỉ đơn giản là gọi Array#length trê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âu COUNT(*) 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, size khô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?

  1. User.all tạo ra một ActiveRecord::Relation.
  2. .length trê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 User và 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ố.
  • 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
  • size:
    • Nếu chưa load → đếm bằng COUNT(*) trên DB (giống count)
    • Nếu đã load → đếm trên mảng trong RAM (giống length)

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí