Проблема N+1 в mongoid
Я использую Mongoid для работы с MongoDB в Rails.
то, что я ищу, - это что-то вроде active record include
. В настоящее время мне не удалось найти такой метод в mongoid orm.
кто-нибудь знает как решить эту проблему в mongoid или, возможно, в mongomapper, который известен как еще один хороший вариант.
8 ответов
обновление: прошло два года с тех пор, как я разместил этот ответ, и все изменилось. См.tybro0103 это для сведения.
Ответ
судя по документации обоих водителей, ни один из них не поддерживает то, что вы ищете. Вероятно, потому что это ничего не решает.
на :include
функциональность ActiveRecord решает задачу N+1 для базы данных SQL. Рассказав ActiveRecord какие связанные таблицы включить, он может построить один SQL-запрос, используя JOIN
заявления. В результате одного вызова базы данных, независимо от количества таблиц, которые вы хотите запросить.
MongoDB позволяет только запрос одной коллекции за раз. Он не поддерживает ничего, как JOIN
. Поэтому, даже если вы можете сказать Mongoid, какие другие коллекции он должен включать, ему все равно придется выполнять отдельный запрос для каждого дополнительного коллекция.
Теперь, когда прошло некоторое время, Mongoid действительно добавил поддержку для этого. См. раздел "нетерпеливая загрузка" here:
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
не делает соединение - SQL и Mongo оба нуждаются в нетерпеливой загрузке.
- проблема N+1 происходит в этом типе сценария (запрос, созданный внутри loop):
.
<% @posts.each do |post| %>
<% post.comments.each do |comment| %>
<%= comment.title %>
<% end %>
<% end %>
похоже, что ссылка ,которую @ amrnt опубликовал, была объединена в Mongoid.
хотя другие ответы верны, в текущих версиях Mongoid метод includes является лучшим способом достижения желаемых результатов. В предыдущих версиях, где includes не был доступен, я нашел способ избавиться от проблемы 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 приводит к проблеме запроса n+2 из записи судьи. в моем случае предоставление серверу dev времени ответа:
завершено 200 OK в 816ms (просмотров: 785.2 ms)
ключ к решению этой проблемы-загрузить пользователей и фотографии в одном запросе вместо 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 для фотографий.
завершено 200 OK в 559ms (просмотров: 87.7 ms)
как эта работа? Что такое IdentityMap?
IdentityMap помогает отслеживать, какие объекты или записи уже были загружены. Поэтому, если вы получите первую запись Пользователя, IdentityMap сохранит ее. Затем, если вы попытаетесь снова получить того же пользователя, Mongoid запросит IdentityMap для пользователя, прежде чем он снова запросит базу данных. Это позволит сохранить 1 запрос в базе данных.
таким образом, загрузив всех пользователей и фотографии, которые мы знаем, мы будем хотеть для судей 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 lazy загружает все запросы, чтобы разрешить это поведение по умолчанию. The :include
тег не нужен.
вам нужно обновить схему, чтобы избежать этого N+1 в MongoDB нет решения для выполнения некоторых соединений.
в моем случае у меня не было всей коллекции, но ее объект, который вызвал n+1 (пуля говорит об этом).
Так, а не писать ниже, что вызывает n+1
quote.providers.officialname
я писал
Quote.includes(:provider).find(quote._id).provider.officialname
это не вызвало проблемы, но оставило меня думать, если я повторюсь или проверка n+1 не нужна для mongoid.