Fix N + 1 query
Bài đăng này đã không được cập nhật trong 5 năm
1. Mở đầu
Như mình đã giới thiệu trong bài trước về N + 1 và làm sao để phát hiện N + 1 Query Tại đây. Thì hôm nay, mình sẽ giới thiệu về các phương pháp để fix N + 1
2. Cách khác phục
2.1 Sử dụng select in ()
(preload)
Library.preload(:books)
Library Load (0.2ms) SELECT "libraries".* FROM "libraries"
Book Load (0.4ms) SELECT "books".* FROM "books" WHERE "books"."library_id" IN (1, 2, 3)
Chúng ta cần 2 truy vấn:
- Truy vấn đầu tiên để load toàn bộ Library
- Truy vấn thứ 2 để load các Books tương ứng với những Libraries đó.
Note:
- Khi dùng
SELECT IN()
các bạn nên cẩn thận với số lượng id trongIN()
Vì trường hợp số lượng ids này quá lớn sẽ dẫn đến performance của bạn sẽ bị giảm. - Bạn nên sử dụng
SELECT IN()
trong trường hợp số lượng ids cố định và ko quá nhiều. Trường hợp nhiều thì bạn nên dùng cách tới đây, đó là sử dụngJOIN
2.2 Sử dụng Join
(eagerload)
Library.eager_load(:books)
SQL (0.7ms) SELECT "libraries"."id" AS t0_r0, "libraries"."name" AS t0_r1, "libraries"."created_at" AS t0_r2, "libraries"."updated_at" AS t0_r3, "libraries"."book_id" AS t0_r4, "books"."id" AS t1_r0, "books"."title" AS t1_r1, "books"."created_at" AS t1_r2, "books"."updated_at" AS t1_r3, "books"."library_id" AS t1_r4 FROM "libraries" LEFT OUTER JOIN "books" ON "books"."library_id" = "libraries"."id"
2.2 Sử dụng includes
- Đối với quan hệ
has_many
ta sử dụng includes class name dạng số nhiều
libraries = Library.where(size: 'large').includes(:books)
- Đối với quan hệ
belongs_to / has_one
ta sử dụng includes class name dạng số it
books = Book.all.includes(:author)
- Đối với trường hợp có nhiều quan hệ cùng một lúc ta dùng
libraries = Library.all.includes(:books, :magazines, :scrolls)
- Đối với trường hợp quan hệ lồng nhau 1 cấp ta sử dụng
libraries = Library.all.includes( books: :author )
- Đối với trường hợp quan hệ lồng nhau 2 cấp ta sử dụng
Cách 1:
libraries = Library.all.includes( books: [ author: :bio ] )
Cách 2:
libraries = Library.all.includes( :books => { :author => :bio } )
- Trường hợp có các quan hệ lồng nhau và các quan hệ thông thường
libraries = Library.all.includes( { :books => :author }, :scrolls, :magazines )
- Đặt
includes
ngay sau câu truy vấn và trước biểu thức tính toán và limit
libraries = Library.where(size: "large").includes(:books).limit(5)
authors = Author.where(genre: "Romantic").includes(:books).limit(5)
- Khi sử dụng full text search với gem Searchkick thì bản thân gem searchkich cũng đã hổ trọ eager loading với cấu trúc sau:
Book.search "Rowling", include: [:author, :publisher]
Note:
- Mặc định của
includes
là dùngselect in
nhưng nếu cần thiết có thể chuyển sangjoins
- SELECT IN
Library.includes(:books)
- JOIN
Library.includes(:books).references(:books)
3. Tài liệu tham khảo
https://medium.com/@codenode/10-tips-for-eager-loading-to-avoid-n-1-queries-in-rails-2bad54456a3f
https://viblo.asia/p/select-in-va-joins-trong-framework-rails-oOVlY4YyZ8W
All rights reserved