在急切加载 Active Record 关联时避免双重查询?

如何解决在急切加载 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 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?