我正在使用 Mongoid 在 Rails 中使用 MongoDB。
我正在寻找的是类似于 active record 的东西include
。目前我未能在 mongoid orm 中找到这种方法。
任何人都知道如何在 mongoid 或 mongomapper 中解决这个问题,这被称为另一个不错的选择。
我正在使用 Mongoid 在 Rails 中使用 MongoDB。
我正在寻找的是类似于 active record 的东西include
。目前我未能在 mongoid orm 中找到这种方法。
任何人都知道如何在 mongoid 或 mongomapper 中解决这个问题,这被称为另一个不错的选择。
现在已经过去了一段时间,Mongoid 确实增加了对此的支持。请参阅此处的“急切加载”部分:http:
//docs.mongodb.org/ecosystem/tutorial/ruby-mongoid-tutorial/#eager-loading
Band.includes(:albums).each do |band|
p band.albums.first.name # Does not hit the database again.
end
我想指出:
:include
不进行连接.
<% @posts.each do |post| %>
<% post.comments.each do |comment| %>
<%= comment.title %>
<% end %>
<% end %>
看起来@amrnt 发布的链接已合并到 Mongoid 中。
更新:自从我发布这个答案已经两年了,事情发生了变化。有关详细信息,请参阅tybro0103 的答案。
根据两个驱动程序的文档,它们都不支持您正在寻找的内容。可能是因为它不会解决任何问题。
ActiveRecord的:include
功能解决了SQL 数据库的 N+1 问题。JOIN
通过告诉 ActiveRecord 要包含哪些相关表,它可以使用语句构建单个 SQL 查询。这将导致单个数据库调用,无论您要查询多少表。
MongoDB 只允许您一次查询一个集合。它不支持像JOIN
. 因此,即使您可以告诉 Mongoid 它必须包含哪些其他集合,它仍然必须为每个附加集合执行单独的查询。
尽管其他答案是正确的,但在当前版本的 Mongoid 中,包含方法是实现预期结果的最佳方法。在包含不可用的先前版本中,我找到了摆脱 n+1 问题的方法,并认为值得一提。
就我而言,这是一个 n+2 问题。
class Judge
include Mongoid::Document
belongs_to :user
belongs_to :photo
def as_json(options={})
{
id: _id,
photo: photo,
user: user
}
end
end
class User
include Mongoid::Document
has_one :judge
end
class Photo
include Mongoid::Document
has_one :judge
end
控制器动作:
def index
@judges = Judge.where(:user_id.exists => true)
respond_with @judges
end
此 as_json 响应会导致来自 Judge 记录的 n+2 查询问题。在我的情况下,给开发服务器一个响应时间:
在 816 毫秒内完成 200 次 OK(查看次数:785.2 毫秒)
解决这个问题的关键是在单个查询中加载用户和照片,而不是每个法官一个一个一个一个地加载。
您可以使用 Mongoids IdentityMap 来执行此操作, Mongoid 2和Mongoid 3支持此功能。
首先在mongoid.yml配置文件中开启identity map:
development:
host: localhost
database: awesome_app
identity_map_enabled: true
现在更改控制器操作以手动加载用户和照片。注意:Mongoid::Relation 记录将延迟评估查询,因此您必须调用 to_a 来实际查询记录并将它们存储在 IdentityMap 中。
def index
@judges ||= Awards::Api::Judge.where(:user_id.exists => true)
@users = User.where(:_id.in => @judges.map(&:user_id)).to_a
@photos = Awards::Api::Judges::Photo.where(:_id.in => @judges.map(&:photo_id)).to_a
respond_with @judges
end
这导致总共只有 3 个查询。评委 1 份,用户 1 份,照片 1 份。
在 559 毫秒内完成 200 次 OK(查看次数:87.7 毫秒)
这是如何运作的?什么是 IdentityMap?
IdentityMap 有助于跟踪已加载的对象或记录。因此,如果您获取第一个用户记录,IdentityMap 将存储它。然后,如果您尝试再次获取相同的用户,Mongoid 会在再次查询数据库之前查询用户的 IdentityMap。这将在数据库上保存 1 个查询。
因此,通过加载所有用户和照片,我们知道我们将在手动查询中需要 Judges json,我们一次将数据预加载到 IdentityMap 中。然后当法官需要它的用户和照片时,它会检查 IdentityMap 并且不需要查询数据库。
ActiveRecord:include
通常不会进行完全连接来填充 Ruby 对象。它执行两个调用。首先获取父对象(例如帖子),然后第二次调用拉取相关对象(属于帖子的评论)。
对于引用的关联,Mongoid 的工作方式基本相同。
def Post
references_many :comments
end
def Comment
referenced_in :post
end
在控制器中,您会收到以下帖子:
@post = Post.find(params[:id])
在您看来,您迭代了评论:
<%- @post.comments.each do |comment| -%>
VIEW CODE
<%- end -%>
Mongoid 将在集合中找到该帖子。当您点击评论迭代器时,它会执行单个查询来获取评论。Mongoid 将查询包装在游标中,因此它是一个真正的迭代器并且不会使内存过载。
Mongoid 延迟加载所有查询以默认允许此行为。:include
标签是不必要的。
在主记录/文档中嵌入详细记录/文档。
您需要更新您的模式以避免这种 N+1 在 MongoDB 中没有解决方案来做一些联合。
就我而言,我没有整个集合,而是一个导致 n+1 的对象(子弹说)。
所以与其在下面写导致 n+1
quote.providers.officialname
我写
Quote.includes(:provider).find(quote._id).provider.officialname
这并没有造成问题,但让我想如果我重复自己或检查 n+1 对于 mongoid 是不必要的。