Взаємовідносини 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? Тоді мені доведеться створити нову колекцію для коментарів?


Усі об’єкти Mongo створюються за допомогою _ID, незалежно від того, створюєте ви поле чи ні. Тож технічно кожен коментар все одно матиме ідентифікатор.
Роббі Гільфойл

25
@RobbieGuilfoyle НЕ true-- см stackoverflow.com/a/11263912/347455
pennstatephil

13
Я виправлений, дякую @pennstatephil :)
Роббі Гільфойл

4
Те , що він , можливо , означає, що все мангуст об'єкти створюються з _id для тих , хто використовує ці рамки - см мангуста subdocs
Лука Steeb

1
Дуже гарною книгою для вивчення монго-db відносин є "Приклади дизайну MongoDB - О'Рейлі". Перший розділ, поговоримо про це рішення, вбудувати чи посилатися?
Феліпе Толедо

Відповіді:


769

Це швидше мистецтво, ніж наука. Монго Документація по Schemas є хорошим довідником, але тут є деякі речі , щоб розглянути наступні питання:

  • Покладіть якомога більше

    Радість бази даних Document полягає в тому, що вона усуває безліч приєднань. Першим вашим інстинктом має бути розміщення стільки, скільки ви можете. Оскільки документи MongoDB мають структуру, і тому що ви можете ефективно здійснювати запит у цій структурі (це означає, що ви можете взяти ту частину потрібного вам документа, тому розмір документа не повинен вас турбувати сильно), немає негайної потреби в нормалізації даних, наприклад ви б у SQL. Зокрема, будь-які дані, які не є корисними, крім батьківського документа, повинні бути частиною цього ж документа.

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

    Це не стільки питання "місця зберігання", скільки питання "узгодженості даних". Якщо багато записів посилаються на одні і ті ж дані, то ефективніше та менше схильність до оновлення однієї записи та збереження посилань на неї в інших місцях.

  • Міркування щодо розміру документа

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

  • Складні структури даних:

    MongoDB може зберігати довільні структури глибоких вкладених даних, але не може їх ефективно шукати. Якщо ваші дані утворюють дерево, ліс або графік, вам потрібно ефективно зберігати кожен вузол та його краї в окремому документі. (Зверніть увагу, що є сховища даних, спеціально розроблені для цього типу даних, які слід також враховувати)

    Також було вказано, що неможливо повернути підмножину елементів у документі. Якщо вам потрібно вибрати кілька бітів кожного документа, їх буде простіше відокремити.

  • Узгодженість даних

    MongoDB здійснює компроміс між ефективністю та послідовністю. Правило полягає в тому, що зміни в одному документі завжди є атомними, тоді як оновлення декількох документів ніколи не слід вважати атомними. Також немає можливості "заблокувати" запис на сервері (ви можете вбудувати це в логіку клієнта, використовуючи, наприклад, поле "замок"). Розробляючи схему, подумайте, як ви будете зберігати свої дані. Як правило, чим більше ви зберігаєте в документі, тим краще.

Для того, що ви описуєте, я б вклав коментарі та дав би кожному коментарю поле id з ObjectID. У ObjectID вкладена часова марка, щоб ви могли використовувати її замість створеної, якщо хочете.


1
Я хотів би додати питання щодо ОП: Моя модель коментарів містить ім'я користувача та посилання на його аватар. Який найкращий підхід, вважаючи, що користувач може змінити своє ім’я / аватар?
user1102018

5
Що стосується "Складних структур даних", то, здається, можливо повернути підмножину елементів у документі за допомогою рамки агрегації (спробуйте $ unind).
Eyal Roth

4
Errr, Ця методика або не була спробована, або не була широко відома в MongoDB на початку 2012 року. Враховуючи популярність цього питання, я б закликав вас написати власну оновлену відповідь. Боюся, що я відійшов від активного розвитку MongoDB, і мені не вдається висловитись з вашими коментарями в моєму первісному дописі.
Джон Ф. Міллер

54
16МБ = 30 мільйонів твітів? тис. менів близько 0,5 байта за твіт ?!
Паоло

8
Так, здається, я вийшов з рахунку в 1000 разів, і деякі люди вважають це важливим. Я відредагую публікацію. WRT 560 байт за твіт, коли я передавав це в 2011 році, щебетати ще були прив’язані до текстових повідомлень та Ruby 1.4; Іншими словами, досі лише символи ASCII.
Джон Ф. Міллер

39

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


10
чи можете ви додайте посилання? Дякую.
db80

Як ви знаходите конкретний коментар до цього дизайну від одного до багатьох?
Маурісіо Пасторіні


29

Якщо я хочу відредагувати зазначений коментар, як отримати його вміст та його питання?

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

Це поверне весь документ питання. Щоб відредагувати зазначений коментар, вам слід знайти коментар клієнта, внести зміни та зберегти його назад у БД.

Загалом, якщо ваш документ містить масив об’єктів, ви виявите, що ці суб’єкти потрібно буде змінити на стороні клієнта.


4
це не спрацює, якщо два коментарі мають однаковий вміст. можна стверджувати, що ми також можемо додати автора до пошукового запиту, який все одно не працюватиме, якби автор зробив два однакові коментарі з однаковим вмістом
Steel Brain

@SteelBrain: якби він зберігав індекс коментарів, позначення крапок може допомогти. дивіться stackoverflow.com/a/33284416/1587329
serv-inc

13
Я не розумію, як ця відповідь має 34 відгуки, другі множини коментують те саме, що вся система зламає. Це абсолютно жахливий дизайн і його ніколи не слід використовувати. Шлях, який @user робить, це шлях
user2073973

21

Ну, я трохи запізнююся, але все ж хотів би поділитися своїм способом створення схем.

У мене є схеми для всього, що можна описати словом, як ви зробили це в класичному ООП.

EG

  • Прокоментуйте
  • Рахунок
  • Користувач
  • Блогпост
  • ...

Кожна схема може бути збережена як документ або піддокумент, тому я декларую це для кожної схеми.

Документ:

  • Може використовуватися як еталон. (Наприклад, користувач зробив коментар -> коментар має посилання "зроблено" користувачем)
  • Це "Корінь" у вашому додатку. (Наприклад, блог -> є сторінка про блог)

Піддокумент:

  • Може використовуватися лише один раз / ніколи не є посиланням. (Наприклад, коментар зберігається в пості)
  • Ніколи не є "Корінь" у вашому додатку. (Коментар відображається на сторінці блогу, але ця сторінка все ще про блог)

20

Я натрапив на цю невелику презентацію, досліджуючи це питання самостійно. Я був здивований тим, наскільки добре він був викладений, як інформація, так і презентація.

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

Він підсумував:

Як правило, якщо у вас є багато [дочірніх документів] або якщо вони є великими, найкраща може бути окрема колекція.

Менші та / або менші документи, як правило, є природним придатним для вбудовування.


11
Скільки коштує a lot? 3? 10? 100? Що large? 1кб? 1 Мб? 3 поля? 20 полів? Що таке smaller/ fewer?
Traxo

1
Це гарне запитання, і на одне я не маю конкретної відповіді. Ця ж презентація включала слайд, на якому написано: "Документ, включаючи всі його вбудовані документи та масиви, не може перевищувати 16 МБ", так що це може бути вашим відсіканням або просто перейти з тим, що здається розумним / зручним для вашої конкретної ситуації. У моєму поточному проекті більшість вбудованих документів стосуються відносин 1: 1 або 1: багато, де вбудовані документи справді прості.
Кріс Блум

Дивіться також поточний головний коментар від @ john-f-miller, який, хоча і не надає конкретних цифр для порогового значення, містить деякі додаткові вказівки, які можуть допомогти вам керувати вашим рішенням.
Кріс Блум

16

Я знаю, що це досить старе, але якщо ви шукаєте відповідь на питання ОП про те, як повернути лише вказаний коментар, ви можете скористатися оператором $ (запиту) таким чином:

db.question.update({'comments.content': 'xxx'}, {'comments.$': true})

4
це не спрацює, якщо два коментарі мають однаковий вміст. можна стверджувати, що ми також можемо додати автора до пошукового запиту, який все одно не працюватиме, якби автор зробив два однакові коментарі з однаковим вмістом
Steel Brain

1
@SteelBrain: Добре зіграний сер, добре зіграний.
JakeStrang

12

Так, ми можемо використовувати посилання в document.To заповнити інший документ , так само , як SQL я joins.In Монго дб вони не мають приєднується до відображення одного до багатьох відношеннях document.Instead , що ми можемо використовувати Заповнити , щоб виконати наш сценарій ..

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


5
Мангуст видасть окремий запит для кожного заселеного поля. Це відрізняється від SQL JOINS, оскільки вони виконуються на сервері. Сюди входить додатковий трафік між сервером додатків та сервером mongodb. Знову ж, ви можете це врахувати під час оптимізації. Тим не менш, ваш віщувач все ще правильний.
Макс

6

Насправді мені дуже цікаво, чому ніхто не говорив про технічні характеристики UML. Основне правило полягає в тому, що якщо у вас є агрегація, ви повинні використовувати посилання. Але якщо це композиція, то зчеплення сильніше, і вам слід використовувати вбудовані документи.

І ви швидко зрозумієте, чому це логічно. Якщо об'єкт може існувати незалежно від батьківського, то ви хочете отримати доступ до нього, навіть якщо батьківського не існує. Оскільки ви просто не можете вбудувати його в неіснуючий батьків, ви повинні зробити його живим у власній структурі даних. І якщо батьки існують, просто з'єднайте їх, додавши посилання на об'єкт у батьків.

Ви насправді не знаєте, у чому різниця між двома стосунками? Ось посилання, що їх пояснює: Агрегація проти складу в UML


Чому -1? Будь ласка, дайте пояснення, яке б прояснило причину
Bonjour123


1

Якщо я хочу відредагувати зазначений коментар, як я можу отримати його вміст та його питання?

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

Ви можете зробити f.ex.

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

(як ще один спосіб редагувати коментарі всередині запитання)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.