如何避免多个查询:包含在Rails中?

时间:2011-06-06 00:17:12

标签: ruby-on-rails activerecord

如果我这样做

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

执行两个查询(一个用于发布数据,另一个用于帖子的评论)。然后,当我发布post.com时,不会执行另一个查询,因为数据已经被缓存。

有没有办法只做一个查询并仍然通过post.comments访问评论?

2 个答案:

答案 0 :(得分:32)

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

例如,请考虑以下情形:Post模型有3个字段需要选择,2个字段用于Comment,此特定帖子有100条评论。 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的方法对性能要好得多。

答案 1 :(得分:5)

如果使用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)