【问题标题】:How do I avoid multiple queries with :include in Rails?如何避免在 Rails 中使用 :include 进行多次查询?
【发布时间】:2011-06-06 00:17:12
【问题描述】:

如果我这样做

post = Post.find_by_id(post_id, :include => :comments)

执行两个查询(一个用于发布数据,另一个用于发布的 cmets)。然后当我做 post.cmets 时,由于数据已经缓存,所以没有执行另一个查询。

有没有办法只做一个查询,仍然通过 post.cmets 访问 cmets?

【问题讨论】:

  • 这似乎不可能。我尝试在 cmets 上使用外连接。那确实只执行一个查询,但结果没有被缓存。所以,当我调用post.comments 时,它会执行另一个查询。如果其他人对此提出答案,我会很感兴趣。

标签: ruby-on-rails activerecord


【解决方案1】:

不,没有。这是:include 的预期行为,因为JOIN 方法最终证明是低效的。

例如,考虑以下场景:Post 模型有 3 个需要选择的字段,Comment 有 2 个字段,并且这个特定的帖子有 100 个 cmets。 Rails 可以按照以下方式运行单个 JOIN 查询:

SELECT post.id, post.title, post.author_id, comment.id, comment.body
FROM posts
INNER JOIN comments ON comment.post_id = post.id
WHERE post.id = 1

这将返回以下结果表:

 post.id | post.title | post.author_id | comment.id | comment.body
---------+------------+----------------+------------+--------------
       1 | Hello!     |              1 |          1 | First!
       1 | Hello!     |              1 |          2 | Second!
       1 | Hello!     |              1 |          3 | Third!
       1 | Hello!     |              1 |          4 | Fourth!
...96 more...

您已经可以看到问题所在。单查询JOIN 方法虽然返回了您需要的数据,但会冗余返回。当数据库服务器将结果集发送到 Rails 时,它将发送帖子的 ID、标题和作者 ID 各 100 次。现在,假设Post 有 10 个您感兴趣的字段,其中 8 个是文本块。哇。这是很多数据。将数据从数据库传输到 Rails确实需要双方的工作,包括 CPU 周期和 RAM,因此最大限度地减少数据传输对于使应用程序运行得更快、更精简非常重要。

Rails 开发人员对数字进行了处理,大多数应用程序在使用多个查询时运行得更好,这些查询只获取每个数据位一次,而不是一个有可能变得非常冗余的查询。

当然,在每个开发人员的一生中,都会有一段时间需要连接才能运行复杂的条件,这可以通过将 :include 替换为 :joins 来实现。然而,对于预取关系,Rails 采用:include 的方法在性能方面要好得多。

【讨论】:

  • 嘿,Matchu,在给定 has_one 关系的情况下,看起来 :joins 方法比 :include 更有效。这是真的,还是我应该继续使用 :include?​​span>
  • @rayban:很有趣。我怀疑这会很好用,但我不知道:joins 是否正确实例化了其他对象,或者它是否最终为每个对象触发了另一个查询(即使它真的不应该) t)。可能值得运行你自己的基准来找出:/
【解决方案2】:

如果您使用热加载关联的this behaviour,您将获得一个单一(且高效)的查询。

这是一个例子:

  • 假设您有以下模型(其中:user 是外部引用):

    class Item < ActiveRecord::Base
      attr_accessible :name, :user_id
      belongs_to :user
    end
    
  • 然后执行此操作(注意where 部分至关重要,因为它会欺骗 Rails 生成单个查询):

    @items = Item.includes(:user).where("users.id IS NOT NULL").all
    

    将产生单个 SQL 查询(下面的语法是 PostgreSQL 的语法):

    SELECT "items"."id" AS t0_r0, "items"."user_id" AS t0_r1, 
            "items"."name" AS t0_r2, "items"."created_at" AS t0_r3,
            "items"."updated_at" AS t0_r4, "users"."id" AS t1_r0, 
            "users"."email" AS t1_r1, "users"."created_at" AS t1_r4, 
            "users"."updated_at" AS t1_r5 
    FROM "measurements" 
    LEFT OUTER JOIN "users" ON "users"."id" = "items"."user_id" 
    WHERE (users.id IS NOT NULL)

【讨论】:

  • 有效,但你真的不应该这样做。当需要条件时,使用条件。如果不是,请相信默认的 Rails 行为。非常聪明的人对此进行了非常仔细的审查。
  • 是的,好点。但这确实让我对他们做出的决定背后的原因感兴趣。我想一个解释会很受欢迎。我问了this question,希望能将其清除。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-09-28
  • 2022-07-05
  • 2016-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多