如何解决在急切加载 Active Record 关联时避免双重查询?
为了进行单个数据库查询,我急切地加载 Posts 及其翻译数据(使用 Rails 6 和 mobility (*)),但它产生了 2 个 sql 查询:
# app/models/post.rb
class Post < ApplicationRecord
extend mobility
translates :title,:description,backend: :table
end
# app/controllers/posts_controller.rb
class PostsController < ApplicationRecord
def index
@posts = Post.eager_load(:translations).limit(3)
end
end
<%# app/views/posts/index.html.erb %>
<% @posts.each do |post| %>
<h1><%= post.title %></h1>
<div><%= post.description %></div>
<% end %>
结果:
- 首先,选择所有帖子 ID
- 然后使用 WHERE IN 返回具有这些 ID 的帖子的所有属性
SELECT disTINCT "posts"."id" FROM "posts"
LEFT OUTER JOIN "post_translations"
ON "post_translations"."post_id" = "posts"."id" LIMIT $1 [["LIMIT",3]]
SELECT "posts"."id" AS t0_r0,"posts"."created_at" AS t0_r1,"posts"."updated_at" AS t0_r2,"post_translations"."id" AS t1_r0,"post_translations"."locale" AS t1_r1,"post_translations"."created_at" AS t1_r2,"post_translations"."updated_at" AS t1_r3,"post_translations"."title" AS t1_r4,"post_translations"."description" AS t1_r5,"post_translations"."post_id" AS t1_r6 FROM "posts"
LEFT OUTER JOIN "post_translations"
ON "post_translations"."post_id" = "posts"."id"
WHERE "posts"."id" IN ($1,$2,$3) [["id","00060a7d-b846-5fc5-a372-1fc3462c695c"],["id","008db504-6fb4-5e90-bdca-4293ebe6d920"],"034944c1-4067-5ae5-89aa-4777ef14d66b"]]
如何避免这种带有内存中 ID 的双 sql 语句?
(*) 移动性注意事项
Mobility documentation 有产生单个 sql 语句的示例,但正如 Chris Salzberg 所指出的,在这个示例中根本没有使用它的查询 API,所以不应该是罪魁祸首。为了证明该问题可能与 mobility 无关,而是与 Active Record 本身有关,下面是一个从 mobility 中剥离出来的等效代码,它显示了相同的双重查询问题(注意:这只是为了演示目的,正如我所做的那样想继续使用移动性):
class Post < ApplicationRecord
has_many :translations,->{ where(locale: I18n.locale) }
%i(title description).each do |attr|
define_method(attr) do
translations.first.send(attr)
end
end
class Translation < ApplicationRecord; end
end
<%# app/views/posts/index.html.erb %>
<% Post.eager_load(:translations).limit(3).each do |post| %>
<h1><%= post.title %></h1>
<div><%= post.description %></div>
<% end %>
解决方法
如果您试图从您的集合中获取一些非常具体的属性,那么 CollectionProxy 将为您提供单一查询。如果没有提供任何属性(列),它会在执行 OUTER JOIN
查询之前执行不同的查询。
老实说,我还没有通读整个实现来确认其背后的推理。
但让我告诉你一件事。
2.5.1 :037 > Post.eager_load(:translations).each do |post|
2.5.1 :038 > puts post.title
2.5.1 :039?> end
SQL (0.5ms) SELECT "posts"."id" AS t0_r0,"posts"."title" AS t0_r1,"posts"."created_at" AS t0_r2,"posts"."updated_at" AS t0_r3,"post_translations"."id" AS t1_r0,"post_translations"."title" AS t1_r1,"post_translations"."content" AS t1_r2,"post_translations"."locale" AS t1_r3,"post_translations"."post_id" AS t1_r4,"post_translations"."created_at" AS t1_r5,"post_translations"."updated_at" AS t1_r6 FROM "posts" LEFT OUTER JOIN "post_translations" ON "post_translations"."post_id" = "posts"."id"
title 1
title 2
title 3
title 4
在上述情况下,您可以看到eager_load 执行您所期望的操作。类似的情况,你没有提到所需的属性,我认为当它懒惰评估时,除了 distinct
查询之外,它还会选择一个 OUTER JOIN
查询
2.5.1 :040 > Post.eager_load(:translations)
SQL (0.3ms) SELECT DISTINCT "posts"."id" FROM "posts" LEFT OUTER JOIN "post_translations" ON "post_translations"."post_id" = "posts"."id" LIMIT ? [["LIMIT",11]]
SQL (0.5ms) SELECT "posts"."id" AS t0_r0,"post_translations"."updated_at" AS t1_r6 FROM "posts" LEFT OUTER JOIN "post_translations" ON "post_translations"."post_id" = "posts"."id" WHERE "posts"."id" IN (?,?,?) [["id",1],["id",2],3],4]]
=> #<ActiveRecord::Relation [#<Post id: 1,title: nil,created_at: "2021-07-08 12:42:13",updated_at: "2021-07-09 15:32:48">,#<Post id: 2,created_at: "2021-07-09 15:33:50",updated_at: "2021-07-09 15:33:50">,#<Post id: 3,created_at: "2021-07-09 15:33:55",updated_at: "2021-07-09 15:33:55">,#<Post id: 4,created_at: "2021-07-09 15:33:57",updated_at: "2021-07-09 15:33:57">]>
希望这对您有所帮助。如果有任何评论,请发表,以便我可以澄清。 :)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。