mongoid中的N + 1问题

时间:2010-10-12 08:16:14

标签: ruby-on-rails mongodb mongoid mongomapper

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

我正在寻找的是积极的记录include。目前我在mongoid orm中找不到这样的方法。

任何人都知道如何在mongoid或mongomapper中解决这个问题,这是另一个很好的选择。

8 个答案:

答案 0 :(得分:16)

现在已经过了一段时间,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问题发生在这种情况下(在循环内部生成的查询):
  4. <% @posts.each do |post| %>
      <% post.comments.each do |comment| %>
        <%= comment.title %>
      <% end %>
    <% end %>
    

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

答案 1 :(得分:13)

更新:我发布此答案已经过去了两年,事情已经发生了变化。有关详细信息,请参阅tybro0103's answer


旧答案

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

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

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

答案 2 :(得分:6)

虽然其他答案是正确的,但在当前版本的Mongoid中,include方法是获得所需结果的最佳方法。在之前的版本中,包含不可用,我找到了摆脱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.2ms)

解决此问题的关键是在一个查询中加载用户和照片,而不是每个评判1个1。

您可以使用Mongoids IdentityMap Mongoid 2Mongoid 3支持此功能。

首先打开mongoid.yml配置文件中的身份映射:

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为照片。

在559ms完成200 OK(浏览次数:87.7ms)

这是如何运作的?什么是IdentityMap?

IdentityMap有助于跟踪已加载的对象或记录。因此,如果您获取第一个用户记录,IdentityMap将存储它。然后,如果您再次尝试获取相同的用户,Mongoid会在再次查询数据库之前查询用户的IdentityMap。这将在数​​据库上保存1个查询。

因此,通过加载我们知道在手动查询中我们想要Judges json的所有用户和照片,我们将数据一次性预加载到IdentityMap中。然后,当Judge需要它的用户和照片时,它会检查IdentityMap,而不需要查询数据库。

答案 3 :(得分: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 lazy加载所有查询以默认允许此行为。 <{1}}标记是不必要的。

答案 4 :(得分:1)

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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

答案 7 :(得分:0)

在我的情况下,我没有整个集合,但它的一个对象导致n + 1(子弹说)。

所以不要在下面写下导致n + 1

的内容
quote.providers.officialname

我写了

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

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

相关问题