15

这与一年前的一个问题和变化有关。

我提出了一个应该开箱即用的问题示例,前提是您有可用的 sqlite3:https ://github.com/cairo140/rails-eager-loading-counts-demo

安装说明(用于主分支)

git clone git://github.com/cairo140/rails-eager-loading-counts-demo.git
cd rails-eager-loading-counts-demo
rails s

我在存储库中有更完整的文章,但我的一般问题是这个。

我怎样才能使 Rails 急切加载计数以最大限度地减少全面的数据库查询?

n+1每当您#count在关联上使用时,就会出现问题,尽管在 ActiveRelation 中包含了该关联#includes(:associated)。一种解决方法是使用#length,但这仅在它被调用的对象已经加载时才有效,更不用说我怀疑它复制了 Rails 内部已经完成的某些事情。此外,使用的一个问题#length是,当关联开始时没有加载并且计数就是您所需要的时,它会导致不幸的过载。

从自述文件:

我们可以通过在帖子数组上运行#length 来避免这个问题(见附录),它已经加载了,但是如果有随时可用的 count 也是很好的。它不仅更加一致;它提供了不一定需要加载帖子的访问路径。例如,如果您有一个无论如何都显示计数的部分,但有一半的时间是在加载帖子的情况下调用该部分,而一半的时间没有加载,您将面临以下情况:

  • 使用#count
    • nCOUNT已加载帖子时的样式查询
    • nCOUNT未加载帖子时的样式查询
  • 使用#length
    • 帖子已加载时零额外查询
    • n*未加载帖子时的样式查询

在这两种选择之间,没有占优的选择。但是修改 #count 以推迟到 #length 或访问以其他方式存储在幕后的长度会很好,这样我们就可以有以下场景:

  • 使用修订版#count
    • 帖子已加载时零额外查询
    • nCOUNT未加载帖子时的样式查询

那么这里的正确方法是什么?有没有我忽略的东西(非常非常有可能)?

4

4 回答 4

9

正如@apneadiving 所建议的那样,counter_cache 运行良好,因为计数器列会在添加或删除记录时自动更新。因此,当您加载父对象时,计数将包含在对象中,而无需访问其他表。

但是,如果出于某种原因你不喜欢这种方法,你可以这样做:

Post.find(:all,
          :select => "posts.*, count(comments.id) `comments_count`",
          :joins  => "left join comments on comments.post_id = posts.id")
于 2011-02-19T07:50:38.060 回答
3

Zubin 的另一种方法:

Post.select('posts.*, count(comments.id) `comments_count`').joins(:comments).group('posts.id')
于 2014-02-16T07:35:18.650 回答
2

似乎实现这种功能的最佳方式可能是为您想要的单独的模型和子计数对象创建 SQL 视图(参考:此处此处);及其相关的 ActiveRecord 模型。

您可能会非常聪明,并在原始模型上使用子类化并结合set_table_name :sql_view_name保留对象上的所有原始方法,甚至可能是它们的一些关联。

例如,假设我们要在您的示例中添加“Post.has_many :comments”,就像上面@Zubin 的回答一样;那么一个人可能能够做到:

   class CreatePostsWithCommentsCountsView < ActiveRecord::Migration
      def self.up
        #Create SQL View called posts_with_comments_counts which maps over 
        # select posts.*, count(comments.id) as comments_count from posts 
        #   left outer join comments on comments.post_id = posts.id 
        #   group by posts.id
        # (As zubin pointed out above.) 
        #*Except* this is in SQL so perhaps we'll be able to do further 
        # reducing queries against it *as though it were any other table.*
      end    
   end

   class PostWithCommentsCount < Post         #Here there be cleverness.
                                              #The class definition sets up PWCC 
                                              # with all the regular methods of 
                                              # Post (pointing to the posts table
                                              # due to Rails' STI facility.)

    set_table_name :posts_with_comment_counts #But then we point it to the 
                                              # SQL view instead.
                                              #If you don't really care about
                                              # the methods of Post being in PWCC
                                              # then you could just make it a 
                                              # normal subclass of AR::Base.
   end

   PostWithCommentsCount.all(:include => :user)  #Obviously, this sort of "upward
     # looking" include is best used in big lists like "latest posts" rather than
     # "These posts for this user." But hopefully it illustrates the improved 
     # activerecordiness of this style of solution.
   PostWithCommentsCount.all(:include => :comments) #And I'm pretty sure you 
     # should be able to do this without issue as well. And it _should_ only be 
     # the two queries.
于 2011-02-27T19:17:39.423 回答
2

我设置了一个小 gem,它includes_count向 ActiveRecord 添加了一个方法,它使用 SELECT COUNT 来获取关联中的记录数,而无需使用可能很昂贵的 JOIN(取决于具体情况)。

请参阅https://github.com/manastech/includes-count

希望能帮助到你!

于 2012-02-10T20:36:59.350 回答