加入后如何为返回的每条记录使用预加载数据?
How can I use eager loaded data after join for each record returned?
在我的代码中,我加入了 2 个模型(例如 Post 和评论),然后我渴望加载第三个模型(例如作者)。当我打印每条记录时,rails 为 Post 的第一个实例使用预先加载的模型数据,但为每个额外的 Post[=38= 实例触发额外的数据库查询].这是一个 n+1 的问题。
我不明白的是为什么我只能对每个 post 的第一条记录使用预先加载的数据,而不是连接返回的附加记录?
我看过类似的问题,但不认为这些解决的是完全相同的问题。其中包括:
- why is this rails association loading individually after an eager load?
- belongs_to association loaded individually even after eager loading
- Eager loading not loading in rails,以及其他
我还查看了 rails 源代码,特别是 ActiveRecord::Associations::Preloader 并尝试在本地对其进行一些修改,以查看是否可以缩小问题所在或是否会发生这里。我原以为 class 可能是通过 id 去除了非唯一记录实例(我认为它在第 92 行)但是当我更改它时我的问题没有得到解决。
我确实偶然发现了这个 whacky edge case 这似乎适用,但在我的实际代码中我使用 find_by_sql
并且未能有效地实现它。
我创建了一个 gist with steps to reproduce the issue 我正在体验和得到的输出。任何帮助将不胜感激。
注意:这个示例是我能想到的最简单的设置,它演示了我在实际代码中遇到的相同问题。在我的示例案例中,我知道我可以急于加载评论和作者然而,在我的实际代码中有一个更复杂的连接并且需要连接。我无法从这个示例中急切加载 Comment 模型。
对于那些将来发现这个的人,这里是我发现的和我实现的。
简单的要点示例
对于我要点中概述的简单示例,我发现通过更改以下内容可以获得所需的结果。
要点:
# Triggers n+1 for author after first puts of an author.
posts = Post.joins(:comments).select("posts.*, comments.body").includes(:author)
posts.each do |x|
puts "#{x.title} #{x.author.name} #{x.body}"
end
更改为:
# Works as expected.
posts = Post.joins(:comments, :author).select("posts.*, comments.body").includes(:author)
posts.each do |x|
puts "#{x.title} #{x.author.name} #{x.body}"
end
通过加入 author
,我得到了我想要的结果,无需额外查询。
更复杂的示例和实现
虽然上面解决了我用来演示我的问题的简单示例,但在我更复杂的现实世界问题中(如我的问题中所述)我无法实现这个额外的连接来解决问题。
相反,我实现了这个伟大的 Thoughbot post and in the rails handbook on github (Query Object Pattern and Decorator Pattern) 中概述的查询对象模式和装饰器模式。
这些模式让我获得了预先加载我想要的特定关联的所有好处,但对于我更复杂的查询。
在我的代码中,我加入了 2 个模型(例如 Post 和评论),然后我渴望加载第三个模型(例如作者)。当我打印每条记录时,rails 为 Post 的第一个实例使用预先加载的模型数据,但为每个额外的 Post[=38= 实例触发额外的数据库查询].这是一个 n+1 的问题。
我不明白的是为什么我只能对每个 post 的第一条记录使用预先加载的数据,而不是连接返回的附加记录?
我看过类似的问题,但不认为这些解决的是完全相同的问题。其中包括:
- why is this rails association loading individually after an eager load?
- belongs_to association loaded individually even after eager loading
- Eager loading not loading in rails,以及其他
我还查看了 rails 源代码,特别是 ActiveRecord::Associations::Preloader 并尝试在本地对其进行一些修改,以查看是否可以缩小问题所在或是否会发生这里。我原以为 class 可能是通过 id 去除了非唯一记录实例(我认为它在第 92 行)但是当我更改它时我的问题没有得到解决。
我确实偶然发现了这个 whacky edge case 这似乎适用,但在我的实际代码中我使用 find_by_sql
并且未能有效地实现它。
我创建了一个 gist with steps to reproduce the issue 我正在体验和得到的输出。任何帮助将不胜感激。
注意:这个示例是我能想到的最简单的设置,它演示了我在实际代码中遇到的相同问题。在我的示例案例中,我知道我可以急于加载评论和作者然而,在我的实际代码中有一个更复杂的连接并且需要连接。我无法从这个示例中急切加载 Comment 模型。
对于那些将来发现这个的人,这里是我发现的和我实现的。
简单的要点示例
对于我要点中概述的简单示例,我发现通过更改以下内容可以获得所需的结果。
要点:
# Triggers n+1 for author after first puts of an author.
posts = Post.joins(:comments).select("posts.*, comments.body").includes(:author)
posts.each do |x|
puts "#{x.title} #{x.author.name} #{x.body}"
end
更改为:
# Works as expected.
posts = Post.joins(:comments, :author).select("posts.*, comments.body").includes(:author)
posts.each do |x|
puts "#{x.title} #{x.author.name} #{x.body}"
end
通过加入 author
,我得到了我想要的结果,无需额外查询。
更复杂的示例和实现
虽然上面解决了我用来演示我的问题的简单示例,但在我更复杂的现实世界问题中(如我的问题中所述)我无法实现这个额外的连接来解决问题。
相反,我实现了这个伟大的 Thoughbot post and in the rails handbook on github (Query Object Pattern and Decorator Pattern) 中概述的查询对象模式和装饰器模式。
这些模式让我获得了预先加载我想要的特定关联的所有好处,但对于我更复杂的查询。