当前位置:  开发笔记 > 编程语言 > 正文

mongoid中的N + 1问题

如何解决《mongoid中的N+1问题》经验,为你挑选了4个好方法。

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

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

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



1> tybro0103..:

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

我想指出:

    Rails' :include没有加入

    SQL和Mongo都需要急切加载.

    N + 1问题发生在这种类型的场景中(在循环内部生成的查询):

.

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

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


只是为了添加评论.您应该启用识别地图以使其有效http://mongoid.org/en/mongoid/docs/identity_map.html

2> Niels van de..:

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


老答案

基于两个驱动程序的文档,它们都不支持您正在寻找的内容.可能是因为它无法解决任何问题.

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

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


这是不正确的.Rails'`:include`选项不进行连接.它急切负荷.SQL和Mongo都需要急切加载.它会阻止您在循环中反复查询同一个集合,而是在开始循环之前查询集合一次.

3> brianp..:

虽然其他答案是正确的,但在当前版本的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.2ms)

解决此问题的关键是在单个查询中加载用户和照片,而不是每个Judge 1个1.

您可以使用Mongoids IdentityMap Mongoid 2和Mongoid 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,而不需要查询数据库.



4> 小智..:

ActiveRecord :include通常不会执行完全连接来填充Ruby对象.它做了两个电话.首先获取父对象(比如一个帖子),然后第二次调用拉相关对象(属于Post的注释).

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

推荐阅读
吻过彩虹的脸_378
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有