3 ways to do eager loading (preloading) in Rails
Khi làm việc với các project nhỏ, chúng ta thường ít khi quan tâm đến việc tối ưu hóa truy vẫn vì đối với các project nhỏ thì những table còn ít và quan hệ với nhau còn đơn giản, nên việc project chạy nhanh hay chậm chúng ta khó có thể nhận biết được rõ ràng. Nhưng khi bắt đầu làm project lớn một ...
Khi làm việc với các project nhỏ, chúng ta thường ít khi quan tâm đến việc tối ưu hóa truy vẫn vì đối với các project nhỏ thì những table còn ít và quan hệ với nhau còn đơn giản, nên việc project chạy nhanh hay chậm chúng ta khó có thể nhận biết được rõ ràng. Nhưng khi bắt đầu làm project lớn một chút, lượng dữ liệu nhiều một chút và quan hệ phức tạp thì chúng ta thấy răng, hệ thống của chúng ta khi load dữ liệu thì chậm rõ rệt.
Sau khi tìm hiểu thì mình xin chia sẻ một chút về nhưng gì mình đã tím hiểu được. Chắc hẳn mọi người đều nghe tới eager loading. Sau đây mình xi giới thiệu 3 con đường để thực hiện việc đó.
Preload
preload lấy dữ liệu quan hệ từ những câu truy vấn riêng biệt.
User.preload(:posts).to_a # => SELECT "users".* FROM "users" SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1)
đây là cách includes dữ liệu trong trường hợp mặc định.
Preload sinh ra 2 câu query chúng ta không thể sử dụng bảng posts trong điều kiện where, như thế sẽ báo lỗi. Đây là kết quả nếu chúng ta làm làm điều trên:
User.preload(:posts).where("posts.desc='ruby is awesome'") # => SQLite3::SQLException: no such column: posts.desc: SELECT "users".* FROM "users" WHERE (posts.desc='ruby is awesome')
với preload mệnh đề where có thể đươc viết:
User.preload(:posts).where("users.name='Neeraj'") # => SELECT "users".* FROM "users" WHERE (users.name='Neeraj') SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (3)
includes
Trên thực thế thì đa phần chúng ta thường sử dụng includes(), includes khá là quen thuộc áp dụng eager loading để truy xuất dữ liệu. Includes cung lấy dữ liệu quan hệ từ những câu truy vấn riêng biệt như preload nhưng nó thông minh hơn. ở ví du trên về preload chúng ta có thể thấy preload khong thể thực hiện câu truy vấn :
User.preload(:posts).where("posts.desc='ruby is awesome'")
Giờ mình sẽ thử với includes và kết quả :
User.includes(:posts).where('posts.desc = "ruby is awesome"').to_a # => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "posts"."id" AS t1_r0, "posts"."title" AS t1_r1, "posts"."user_id" AS t1_r2, "posts"."desc" AS t1_r3 FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE (posts.desc = "ruby is awesome")
Nhìn vào đoạn truy vấn trên có thể thấy, includes chuyển từ 2 câu truy vấn riêng biệt thành 1 câu query với LEFT OUTER JOIN để lấy dữ liệu. và nó cũng áp dụng các điều kiện mà preload không thể thực hiện. Includes thay đổi từ hai câu truy vấn thành một truy vấn trong một số trường hợp. Trong trường hợp đơn giản nó sẽ sử dụng hai câu query. Với một số lý do mà bạn muốn sử dụng một câu query thay vì hai câu query như includes sinh ra. Hãy sử dụng references để thực hiện điều đó :
User.includes(:posts).references(:posts).to_a # => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "posts"."id" AS t1_r0, "posts"."title" AS t1_r1, "posts"."user_id" AS t1_r2, "posts"."desc" AS t1_r3 FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
Eager load eager load lấy tất cả các quan hệ trong một câu truy vấn đơn sử dụng LEFT OUTER JOIN.
User.eager_load(:posts).to_a # => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, "posts"."id" AS t1_r0, "posts"."title" AS t1_r1, "posts"."user_id" AS t1_r2, "posts"."desc" AS t1_r3 FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
Đây là chính xác nhưng gì includes làm khi nó bắt buôc phải thực hiện một truy vấn đơn. Khi mệnh đề where hoặc order sử dụng những thuộc tính từ bảng post.
Tổng kết
Eager load đúng là một công cụ hưũ hiệu để thực hiện việc tối ưu hóa performace của hệ thông. Trên đây là những gì mình tìm hiểu được. Chắc chắn còn thiếu rất nhiều các hay và tốt để tối ưu truy vấn nên rất hi vọng nhận được nhiều sự chia sẻ từ mọi người.
**