我正在使用Ruby on Rails 4,我想了解为什么在热切的加载过程中,即使数据被急切加载,也会运行更多的SQL查询。也就是说,我有以下代码以正确的方式加载:comments
:
@articles = @current_user.articles.includes(:comments)
当上面的代码运行时,我使用以下代码“跟踪”记录器中发生的事情:
@articles.each do |article|
logger.debug article.comments
end
然后记录器说:
Article Load (0.4ms) SELECT ...
Comment Load (0.5ms) SELECT ... WHERE `articles`.`id` IN (...)
#<ActiveRecord::Associations::CollectionProxy [#<Comment id: 1, title: "Hello A">, #<Comment id: 2, title: "Hello B">]>
#<ActiveRecord::Associations::CollectionProxy [#<Comment id: 3, title: "Hello A">, #<Comment id: 4, title: "Hello C">]>
#<ActiveRecord::Associations::CollectionProxy [#<Comment id: 5, title: "Hello D">, #<Comment id: 6, title: "Hello E">]>
...
上面的输出表明热切加载正在按预期工作:运行ActiveRecord::Associations::CollectionProxy
时加载article.comments
对象后没有N + 1问题。
但是,当我尝试像下面这样运行代码时(请注意find_by
子句):
@articles.each do |article|
logger.debug article.comments.find_by(:title => "Hello A")
end
然后记录器说:
Article Load (0.4ms) SELECT ...
Comment Load (0.5ms) SELECT ... WHERE `articles`.`id` IN (...)
Comment Load (0.4ms) SELECT ... AND `comments`.`title` = 'HELLO A'
#<Comment id: 1, title: "Hello A">
Comment Load (0.4ms) SELECT ... AND `comments`.`title` = 'HELLO A'
#<Comment id: 3, title: "Hello A">
Comment Load (0.4ms) SELECT ... AND `comments`.`title` = 'HELLO A'
nil
...
上面的输出表明急切加载不按预期工作:为每条评论运行SQL查询。
所以,我的疑问/怀疑是:
find_by
子句使得急切加载不起作用(注意:即使在我通过使用除{{1}以外的子句“过滤”article.comments
的情况下也会发生这种情况}})? find_by
个对象中已加载的数据作为数组处理,以免它碰到数据库吗?!答案 0 :(得分:1)
我怀疑find_by
是硬连接来进行数据库调用。
第一个示例中列出的对象属于CollectionProxy
类型,这意味着您仍然可以对它们进行SQL查询。由于find_by
是ActiveRecord的一部分,因此在Proxy类上调用它应该转到数据库。
我怀疑如果你改变你的代码以在评论集合上使用诸如find_all
的可枚举方法那么你应该没问题,但这不是很有效(find_all
在线性时间内运行)
或者,通过执行以下操作将所有内容转换为单个联接查询:
Article.joins(:comments).where(comments: {title: "My Title")
或者,如果您需要所有文章是否有匹配的评论,您可以简单地为原始评论添加条件:
Article.includes(:comments).where(comments: {title: "My Title")
答案 1 :(得分:1)
只是为了确认:David Underwood是正确的find_by
将进行数据库调用。实际上,find_by
基本上只是where
和take
的包装器,它确实会进行数据库调用。
实现您正在寻找的内容的另一种方法是使用find
方法将集合代理视为数组,如下所示:
@articles.each do |article|
logger.debug article.comments.find {|comment| comment.title == "Hello A"}
end
<强>更新强>
我不得不承认,这个有点像。
以您正在寻找的方式完成此操作的方法是添加另一个has_many
关系,该关系专门包含您想要的过滤条件,如下所示:
class Article < ActiveRecord::Base
has_many :hello_A_comments, -> { where(title: "Hello A") }, class_name: "Comment"
# rest of class
end
然后,您急切地使用这个新关联加载,如下所示:
@articles = @current_user.articles.includes(:hello_A_comments)
这部分非常重要:
您现在可以通过原始的:comments
关联方法访问关联不,而是通过新的hello_A_comments
方法,如下所示:
@articles.first.hello_a_comments
不幸的是,正如你所看到的,这种方法不是很动态,遗憾的是我不知道如何在急切加载的情况下允许关联中的变量条件。 This answer可能是一个很好的资源来看待,但在急切加载的情况下,说实话,我不相信它是可能的。如果这是一个问题,您可能会遇到我之前提到的数组方法。