N+1 Query trong Rails: preload, eager_load, includes và cái giá của sự tiện lợi

N+1 xuất hiện khi ta load một tập record bằng một query, rồi mỗi record lại âm thầm tạo thêm query cho association.

1
2
users = User.limit(100)
users.each { |user| puts user.articles.size }

Kịch bản có thể là:

1
2
3
SELECT * FROM users LIMIT 100;          -- 1 query
SELECT * FROM articles WHERE user_id=1; -- N query tiếp theo
-- ...

Code nhìn sạch, log SQL nhìn như máy in hóa đơn.

preload: nhiều query có chủ đích

1
User.preload(:articles)

Rails thường chạy hai query: một cho users và một cho toàn bộ articles có user_id liên quan. Đây thường là lựa chọn dễ đoán khi không cần filter/order bằng bảng association.

eager_load: một LEFT OUTER JOIN

1
User.eager_load(:articles)

Rails dùng join và dựng object từ result. Có thể filter bảng liên quan, nhưng result SQL bị nhân dòng khi quan hệ one-to-many. Một query không mặc định rẻ hơn hai query; database không tính performance bằng số câu lệnh cho đẹp dashboard.

includes: Rails tự chọn chiến lược

1
User.includes(:articles)

Thông thường hoạt động giống preload. Khi query tham chiếu bảng association, Rails có thể chuyển sang join:

1
2
User.includes(:articles)
.where(articles: { published: true })

Với SQL fragment dạng string, có thể cần references(:articles).

joins không preload association

1
User.joins(:articles)

joins dùng association để filter/join SQL, nhưng không đồng nghĩa association đã được eager load. Sau đó gọi user.articles vẫn có thể tạo N+1.

Chọn bằng dữ liệu, không bằng tín ngưỡng

  • Chỉ cần load association → bắt đầu với preload/includes.
  • Cần filter bằng bảng liên quan → joins, eager_load, hoặc includes phù hợp.
  • Quan hệ lớn → cân nhắc pagination, select ít cột, aggregate hoặc query riêng.
  • Dùng log, strict_loading, Bullet trong development và APM ở production để phát hiện.
1
User.strict_loading.includes(:articles)

Fix N+1 không phải trò “giảm số query bằng mọi giá”. Mục tiêu là giảm tổng chi phí: round trip, số dòng, memory, object allocation và độ phức tạp SQL.

Reference