0

为了进行单个数据库查询,我渴望加载 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 %>

结果:

  • 首先,选择所有 Post 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"], ["id", "034944c1-4067-5ae5-89aa-4777ef14d66b"]]

如何避免这种带有内存中 ID 的双重 SQL 语句?


(*) 注意 mon Mobility

Mobility 文档中有生成单个 SQL 语句的示例,但正如 Chris Salzberg 所指出的,在此示例中根本没有使用它的查询 API,因此不应该是罪魁祸首。为了证明该问题可能与 Mobility 无关,而是与 Active Record 本身有关,下面是从 Mobility 中剥离出来的一些等效代码,它显示了相同的双重查询问题(注意:这只是为了演示目的,就像我一样想继续使用 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 %>
4

1 回答 1

1

如果您试图从您的集合中获取一些非常具体的属性,那么 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, "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" WHERE "posts"."id" IN (?, ?, ?, ?)  [["id", 1], ["id", 2], ["id", 3], ["id", 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, title: nil, created_at: "2021-07-09 15:33:50", updated_at: "2021-07-09 15:33:50">, #<Post id: 3, title: nil, created_at: "2021-07-09 15:33:55", updated_at: "2021-07-09 15:33:55">, #<Post id: 4, title: nil, created_at: "2021-07-09 15:33:57", updated_at: "2021-07-09 15:33:57">]>

希望这在某种程度上有所帮助。如果有任何评论,请发表,所以我可以澄清一下。:)

于 2021-07-09T15:39:42.457 回答