12

我正在使用 Mongoid 在 Rails 中使用 MongoDB。

我正在寻找的是类似于 active record 的东西include。目前我未能在 mongoid orm 中找到这种方法。

任何人都知道如何在 mongoid 或 mongomapper 中解决这个问题,这被称为另一个不错的选择。

4

8 回答 8

17

现在已经过去了一段时间,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

我想指出:

  1. Rails:include不进行连接
  2. SQL 和 Mongo 都需要预先加载。
  3. N+1 问题发生在这种场景中(在循环内生成查询):

.

<% @posts.each do |post| %>
  <% post.comments.each do |comment| %>
    <%= comment.title %>
  <% end %>
<% end %>

看起来@amrnt 发布的链接已合并到 Mongoid 中。

于 2011-10-11T21:42:47.177 回答
12

更新:自从我发布这个答案已经两年了,事情发生了变化。有关详细信息,请参阅tybro0103 的答案


旧答案

根据两个驱动程序的文档,它们都不支持您正在寻找的内容。可能是因为它不会解决任何问题

ActiveRecord的:include功能解决了SQL 数据库的 N+1 问题。JOIN通过告诉 ActiveRecord 要包含哪些相关表,它可以使用语句构建单个 SQL 查询。这将导致单个数据库调用,无论您要查询多少表。

MongoDB 只允许您一次查询一个集合。它不支持像JOIN. 因此,即使您可以告诉 Mongoid 它必须包含哪些其他集合,它仍然必须为每个附加集合执行单独的查询。

于 2010-10-12T08:39:56.170 回答
8

尽管其他答案是正确的,但在当前版本的 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 2Mongoid 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 并且不需要查询数据库。

于 2012-10-23T06:22:22.537 回答
5

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标签是不必要的。

于 2010-12-03T16:58:39.783 回答
1

这可以帮助https://github.com/flyerhzm/mongoid-eager-loading

于 2011-06-29T13:56:42.283 回答
0

在主记录/文档中嵌入详细记录/文档。

于 2010-10-13T18:50:15.233 回答
0

您需要更新您的模式以避免这种 N+1 在 MongoDB 中没有解决方案来做一些联合。

于 2010-10-12T09:39:43.333 回答
0

就我而言,我没有整个集合,而是一个导致 n+1 的对象(子弹说)。

所以与其在下面写导致 n+1

quote.providers.officialname

我写

Quote.includes(:provider).find(quote._id).provider.officialname

这并没有造成问题,但让我想如果我重复自己或检查 n+1 对于 mongoid 是不必要的。

于 2014-07-29T23:11:07.100 回答