Remove N+1 queries in your Ruby on Rails app
Bài đăng này đã không được cập nhật trong 3 năm
I.Giới thiệu
Bao giờ tự hỏi tại sao mình tải trang chậm hơn khi bạn cần tải những đoạdữ liệu lặp đi lặp lại hay lấy dữ liệu từ một tập các dữ liệu? Câu trả lời có thể là vì bạn có vấn đề với N + 1 queries
, điều đó làm chậm trang web của bạn đáng kể. Nhưng N + 1 là gì và vấn đề là làm thế nào để giải quyết nó? Sau đây tôi sẽ giúp các bạn có một cái nhìn tổng quan về nó.
II. Ví dụ minh họa
N+1 query problem is a situation when you are making an extra call(s) to database when you want to get a specific associated data over and over again. For example: N+1 query trong tình huống này là khi bạn truy ván nhiều lần vào database khi bạn muốn lấy một trường cụ thể của nhiều bản ghi. Ví dụ:
Ở ví dụ này, chúng ta lấy ra items rồi lặp lại các records của chúng và cố gắng lấy ra items category từ database. Tưởng tượng điều này xảy ra với khoảng 50 tới 100 mục thì các model sẽ trở nên cồng kềnh.
III. Cách giải quyết thông dụng
1. Preload
Đầu tiên là sử dụng eager loading methods
Preload là trường hợp mặc định của phương thức includes - nó tạo ra 2 truy vấn tách biệt, một cho truy vấn chỉnh và một cho truy vấn các dữ liệu quan hệ. Điều đó có nghĩa là chúng ta không thể thêm #where({ categories: { title: “Fruits” } }), và nó sẽ báo lỗi.
Example will load all items with preloaded category. Ví dụ trên sẽ load tất cả các itemse với các category được load ra
2. Includes
Thay vì preload
, includes
chọn để rạo ra 1 truy vấn dựa vào tình huống cụ thể - nếu bạn thêm quan hệ ở câu truy vấn "#where". Điều này sẽ tạo ra nhiều hơn truy vấn SQL phức tạp hơn:
Example will load only items with preloaded category which title is Fruits. You can also force #includes to make one query with #references(using LEFT OUTER JOIN) method: Ví dụ trên sẽ chỉ load các items với prreload category mà title là "Fruits" Bạn có thể dùng #includes để tạo ra một truy vấn với references(sử dụng LEFT OUTER JOIN):
3. Eager_load
Phương thức này giống như việc kết hợp #includes
và #references
, nó tạo ra 1 truy vấn vLEFLEFFT OUTER JOIN:
Có một số N+1 xuất hiện và chúng ta khá dễ dàng để chú ý nếu bạn nhìn vào Rails console logs nhưng có một vài truy vấn chúng ta không thể nhìn thấy hay khó để cảnh báo.
IV. Cách giải quyết khác
Một vài giải pháp khác giúp bạn giải quyết vấn đề với N+1 queries:
** Dynamic condition** Ví dụ:
nó sẽ gọi trong một trang trong vòng cả tháng, do vậy 30 queries sẽ đếm mỗi ngày. Nó có thể được sửa lại như sau:
nó sẽ tạo ra instance variable với hash mà keys là ngày và giá trị là biến cố. Tất cả chỉ trong 1 query.
** Join nhiều queries thành một **
Có thể 1 vài truy vấn không thể nhóm lại nhưng có thể join trong các SQL level. Khi nó là những thứ khá phức tạp, bạn không muốn dựa vào ActiveRecord. Thay vào đó, bạn sử dụng SQL để lấy được thứ bạn cần từ database (nhưng bạn cần chú ý cẩn thận nếu sau này bạn có thay đổi kiểu database, nó có thể không hoạt động nữa). Ví dụ:
bạn có thể gộp lại thành 1 query như sau:
V. Bullet
Bullet là một gem giúp bạn phát hiện và hỗ trợ bạn sửa các truy vấn N+1 khá tốt. Nó khá dễ cài đặt và sử dụng. Bạn có thể tìm hiểu và cài đặt Bullet tại đây github.
VI. Tổng kết
Rails là một ngôn ngữ khá dễ học và sử dụng nhanh chóng để viết các trang web. Nhưng dù gì thì nó vẫn cần những truy vấn database. Vì vậy nếu bạn muốn trang cảu bạn chạy nhanh hơn thì cần chú ý tới vấn đề N+1 queries.
VII. Tài liệu tham khảo
All rights reserved