Связи MongoDB: вставлять или ссылаться?

524

Я новичок в MongoDB - из реляционной базы данных. Я хочу разработать структуру вопроса с некоторыми комментариями, но я не знаю, какое отношение использовать для комментариев: embedили reference?

Вопрос с некоторыми комментариями, например stackoverflow , будет иметь такую ​​структуру:

Question
    title = 'aaa'
    content = bbb'
    comments = ???

Сначала я хочу использовать встроенные комментарии (я думаю, что embedрекомендуется в MongoDB), например:

Question
    title = 'aaa'
    content = 'bbb'
    comments = [ { content = 'xxx', createdAt = 'yyy'}, 
                 { content = 'xxx', createdAt = 'yyy'}, 
                 { content = 'xxx', createdAt = 'yyy'} ]

Это понятно, но меня беспокоит этот случай: если я хочу отредактировать указанный комментарий, как мне получить его содержание и вопрос? Нет ни того, _idчтобы позволить мне найти его, ни question_refпозволить мне найти его вопрос. (Я настолько новичок, что не знаю, есть ли способ сделать это без _idи question_ref.)

Должен ли я использовать refнет embed? Тогда я должен создать новую коллекцию для комментариев?

Freewind
источник
Все объекты Mongo создаются с _ID независимо от того, создаете ли вы поле или нет. Так что технически у каждого комментария все еще будет идентификатор.
Робби Гилфойл
25
@RobbieGuilfoyle не true-- см stackoverflow.com/a/11263912/347455
pennstatephil
14
Я исправляюсь, спасибо @pennstatephil :)
Робби Гилфойл
4
Возможно, он имеет в виду, что все объекты mongoose созданы с _id для тех, кто использует эту платформу - см. Поддокументы mongoose
Luca Steeb
1
Очень хорошая книга для изучения отношений mongo db - «Шаблоны прикладного проектирования MongoDB - O'Reilly». Глава первая, поговорим об этом решении, встраивать или ссылаться?
Фелипе Толедо

Ответы:

769

Это больше искусство, чем наука. Монго Документация по Schemas является хорошим справочником, но здесь есть некоторые вещи , чтобы рассмотреть следующие вопросы:

  • Положите как можно больше

    Радость базы данных документов заключается в том, что она исключает множество объединений. Ваш первый инстинкт должен состоять в том, чтобы разместить как можно больше в одном документе. Поскольку документы MongoDB имеют структуру и поскольку вы можете эффективно выполнять запросы внутри этой структуры (это означает, что вы можете взять ту часть документа, которая вам нужна, поэтому размер документа не должен вас сильно беспокоить), нет необходимости в немедленной нормализации данных, таких как вы бы в SQL. В частности, любые данные, которые не являются полезными, кроме его родительского документа, должны быть частью одного и того же документа.

  • Отдельные данные, на которые можно ссылаться из нескольких мест, в свою собственную коллекцию.

    Это не столько проблема «пространства хранения», сколько проблема «согласованности данных». Если многие записи ссылаются на одни и те же данные, более эффективно и менее подвержено ошибкам обновлять одну запись и хранить ссылки на нее в других местах.

  • Размер документа

    MongoDB накладывает ограничение на размер в 4 МБ (16 МБ с 1,8) для одного документа. В мире ГБ данных это звучит мало, но это также 30 тысяч твитов или 250 типичных ответов переполнения стека или 20 мерцающих фотографий. С другой стороны, это гораздо больше информации, чем можно было бы представить за один раз на типичной веб-странице. Сначала подумайте, что облегчит ваши запросы. Во многих случаях забота о размерах документов будет преждевременной оптимизацией.

  • Сложные структуры данных:

    MongoDB может хранить произвольные глубоко вложенные структуры данных, но не может эффективно их искать. Если ваши данные образуют дерево, лес или график, вам необходимо хранить каждый узел и его ребра в отдельном документе. (Обратите внимание, что существуют хранилища данных, специально предназначенные для этого типа данных, которые также следует учитывать)

    Также было указано, что невозможно вернуть подмножество элементов в документе. Если вам нужно выбрать несколько бит каждого документа, их будет легче выделить.

  • Согласованность данных

    MongoDB делает компромисс между эффективностью и последовательностью. Правило состоит в том, что изменения в одном документе всегда являются атомарными, в то время как обновления нескольких документов никогда не следует считать атомарными. Также нет способа «заблокировать» запись на сервере (вы можете встроить это в логику клиента, используя, например, поле «блокировка»). При разработке схемы подумайте, как вы будете поддерживать согласованность данных. Как правило, чем больше вы храните в документе, тем лучше.

Для того, что вы описываете, я бы вставил комментарии и дал каждому комментарию поле id с ObjectID. ObjectID имеет встроенную метку времени, поэтому вы можете использовать ее вместо созданной в, если хотите.

Джон Ф. Миллер
источник
1
Я хотел бы добавить к вопросу OP: Моя модель комментариев содержит имя пользователя и ссылку на его аватар. Каков наилучший подход, учитывая, что пользователь может изменить свое имя / аватар?
user1102018
5
Что касается «сложных структур данных», кажется, что можно вернуть подмножество элементов в документе, используя структуру агрегации (попробуйте $ unwind).
Эяль Рот
4
Errr, этот метод либо не был возможен, либо не был широко известен в MongoDB в начале 2012 года. Учитывая популярность этого вопроса, я бы посоветовал вам написать собственный обновленный ответ. Боюсь, что я отошел от активной разработки на MongoDB, и я не в состоянии ответить на ваш комментарий в моем исходном сообщении.
Джон Ф. Миллер
54
16 МБ = 30 миллионов твитов? это около 0,5 байтов за твит ?!
Паоло
8
Да, кажется, я был в 1000 раз, и некоторые люди считают это важным. Я буду редактировать пост. WRT 560 байт на твит, когда я проверял это в 2011 году, твиттер все еще был привязан к текстовым сообщениям и строкам Ruby 1.4; другими словами, все еще только символы ASCII.
Джон Ф. Миллер,
39

В общем, встраивание хорошо, если у вас есть отношения один-к-одному или один-ко-многим между сущностями, и ссылка хороша, если у вас есть отношения многие-ко-многим.

ywang1724
источник
10
Можете ли вы добавить ссылку ссылку? Спасибо.
db80
Как вы находите конкретный комментарий с этим дизайном один ко многим?
Маурисио Пасторини
coderwall.com/p/px3c7g/…
Кахердин
29

Если я хочу отредактировать указанный комментарий, как получить его содержание и вопрос?

Вы можете запросить по поддокументу: db.question.find({'comments.content' : 'xxx'}).

Это вернет весь документ с вопросом. Чтобы отредактировать указанный комментарий, вам нужно найти комментарий на клиенте, выполнить редактирование и сохранить его обратно в БД.

В общем, если ваш документ содержит массив объектов, вы обнаружите, что эти подчиненные объекты необходимо будет изменить на стороне клиента.

Гейтс В.П.
источник
4
это не будет работать, если два комментария имеют одинаковое содержание. Можно утверждать, что мы могли бы также добавить автора к поисковому запросу, который все равно не сработал бы, если бы автор сделал два одинаковых комментария с одинаковым содержанием
Steel Brain
@SteelBrain: если бы он сохранил индекс комментариев, точечная нотация могла бы помочь. см. stackoverflow.com/a/33284416/1587329
serv-inc
13
Я не понимаю, как в этом ответе 34 отзыва, вторые несколько человек комментируют то же самое, что сломает вся система. Это абсолютно ужасный дизайн и никогда не должен использоваться. То, как @user делает это, это путь
user2073973
21

Ну, я немного опоздал, но все же хотел бы поделиться своим способом создания схемы.

У меня есть схемы для всего, что можно описать одним словом, как если бы вы делали это в классическом ООП.

НАПРИМЕР

  • Комментарий
  • Счет
  • пользователь
  • Сообщение блога
  • ...

Каждая схема может быть сохранена как документ или вложенный документ, поэтому я объявляю это для каждой схемы.

Документ:

  • Может быть использован в качестве ссылки. (Например, пользователь оставил комментарий -> комментарий имеет ссылку «сделано пользователем»)
  • Является ли «рут» в вашем приложении. (Например, пост блога -> есть страница о посте блога)

поддокумент:

  • Может использоваться только один раз / никогда не является ссылкой. (Например, комментарий сохраняется в посте)
  • Никогда не является «корнем» в вашем приложении. (Комментарий только появляется на странице поста, но страница по-прежнему о посте)
Силом
источник
20

Я наткнулся на эту небольшую презентацию, исследуя этот вопрос самостоятельно. Я был удивлен тем, насколько хорошо это было продумано, и информация и представление этого.

http://openmymind.net/Multiple-Collections-Versus-Embedded-Documents

Это подытожило:

Как правило, если у вас много [дочерних документов] или они большие, лучше всего использовать отдельную коллекцию.

Меньшие и / или меньшее количество документов, как правило, подходят для встраивания.

Крис Блум
источник
11
Сколько стоит a lot? 3? 10? 100? Что large? 1kb? 1 МБ? 3 поля? 20 полей? Что такое smaller/ fewer?
Traxo
1
Это хороший вопрос, на который у меня нет конкретного ответа. В ту же презентацию был включен слайд с надписью «Документ, включая все встроенные документы и массивы, не может превышать 16 МБ», так что это может быть вашим отсечением или просто пойти на то, что кажется разумным / удобным для вашей конкретной ситуации. В моем текущем проекте большинство встроенных документов предназначены для отношений 1: 1 или 1: многие, где встроенные документы действительно просты.
Крис Блум
См. Также текущий верхний комментарий @ john-f-miller, который, хотя и не предоставляет конкретных чисел для порога, содержит некоторые дополнительные указатели, которые должны помочь в принятии вашего решения.
Крис Блум
16

Я знаю, что это довольно старо, но если вы ищете ответ на вопрос ОП о том, как вернуть только указанный комментарий, вы можете использовать оператор $ (query), например:

db.question.update({'comments.content': 'xxx'}, {'comments.$': true})
finspin
источник
4
это не будет работать, если два комментария имеют одинаковое содержание. Можно утверждать, что мы могли бы также добавить автора к поисковому запросу, который все равно не работал бы, если бы автор сделал два одинаковых комментария с одинаковым содержанием
Steel Brain
1
@SteelBrain: Хорошо сыграно, сэр, хорошо сыграно.
JakeStrang
12

Да, мы можем использовать ссылку в документе. Чтобы заполнить другой документ точно так же, как sql i joins. В mongo db у них нет соединений для сопоставления документа отношения один ко многим. Вместо этого мы можем использовать populate для выполнения нашего сценария.

var mongoose = require('mongoose')
  , Schema = mongoose.Schema

var personSchema = Schema({
  _id     : Number,
  name    : String,
  age     : Number,
  stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});

var storySchema = Schema({
  _creator : { type: Number, ref: 'Person' },
  title    : String,
  fans     : [{ type: Number, ref: 'Person' }]
});

Заполнение - это процесс автоматической замены указанных путей в документе документами из других коллекций. Мы можем заполнить один документ, несколько документов, простой объект, несколько простых объектов или все объекты, возвращаемые из запроса. Давайте посмотрим на некоторые примеры.

Лучше вы можете получить больше информации, пожалуйста, посетите: http://mongoosejs.com/docs/populate.html

Narendran
источник
5
Mongoose выдаст отдельный запрос для каждого заполненного поля. Это отличается от SQL JOINS, поскольку они выполняются на сервере. Это включает дополнительный трафик между сервером приложений и сервером mongodb. Опять же, вы можете рассмотреть это, когда вы оптимизируете. Тем не менее, ваш ответ по-прежнему правильно.
Макс
6

На самом деле, мне довольно любопытно, почему никто не говорил о спецификациях UML. Практическое правило заключается в том, что если у вас есть агрегация, то вы должны использовать ссылки. Но если это композиция, то связь сильнее, и вы должны использовать встроенные документы.

И вы быстро поймете, почему это логично. Если объект может существовать независимо от родителя, вы захотите получить к нему доступ, даже если родитель не существует. Поскольку вы просто не можете встроить его в несуществующий родительский объект, вы должны включить его в свою собственную структуру данных. И если родитель существует, просто свяжите их вместе, добавив ссылку на объект в родительском.

Не знаете, в чем разница между этими двумя отношениями? Вот ссылка, объясняющая их: Агрегация против Композиции в UML

Bonjour123
источник
Почему -1? Пожалуйста, дайте объяснение, которое разъяснит причину
Bonjour123
1

Если я хочу отредактировать указанный комментарий, как мне получить его содержание и вопрос?

Если вы отслеживали количество комментариев и индекс комментария, который хотите изменить, вы можете использовать оператор точки ( пример SO ).

Вы могли бы сделать, например,

db.questions.update(
    {
        "title": "aaa"       
    }, 
    { 
        "comments.0.contents": "new text"
    }
)

(как еще один способ редактировать комментарии внутри вопроса)

Serv-вкл
источник