Отношения в документно-ориентированной базе данных? - PullRequest
18 голосов
/ 25 февраля 2010

Мне интересны ориентированные на документы базы данных, и я бы хотел поиграть с MongoDB. Поэтому я запустил довольно простой проект (средство отслеживания проблем), но мне трудно думать нереляционным образом.

Мои проблемы:

  1. У меня есть два объекта, которые связаны друг с другом (например, issue = {code:"asdf-11", title:"asdf", reporter:{username:"qwer", role:"manager"}} - здесь у меня есть пользователь, связанный с этой проблемой). Должен ли я создать другой документ «пользователь» и сослаться на него в «выпускном» документе по его идентификатору (как в реляционных базах данных), или я должен оставить все данные пользователя в поддокументе?

  2. Если у меня есть объекты (под-документы) в документе, могу ли я обновить их все в одном запросе?

Ответы [ 6 ]

4 голосов
/ 05 ноября 2011

Я совершенно новичок в документно-ориентированных базах данных, и сейчас я пытаюсь разработать своего рода CMS с использованием node.js и mongodb, поэтому я сталкиваюсь с теми же проблемами, что и вы.

Методом проб и ошибок я нашел это правило: я делаю коллекцию для каждой сущности, которая может быть «предметом» для моих запросов, в то же время встраивая остальное в другие объекты.

Например, комментарии в записи блога могут быть встроены, потому что обычно они связаны с самой записью, и я не могу думать о полезном запросе, сделанном глобально для всех комментариев. С другой стороны, теги, прикрепленные к сообщению, могут заслуживать отдельной коллекции, потому что даже если они привязаны к сообщению, вам может потребоваться глобально рассуждать обо всех тегах (например, составляя список популярных тем). *

1 голос
/ 09 мая 2013

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

Для вашего примера

issue = {code:"asdf-11", title:"asdf", reporter:{username:"qwer", role:"manager"}}

Я бы сделал выпуск и доложил каждому из них свой документ, а также дал бы ссылку на репортера в номере. Вы также можете сослаться на список проблем в репортере. Таким образом, вы не будете дублировать репортеров в вопросах, вы можете запрашивать их каждый в отдельности, вы можете запрашивать репортера по проблеме, и вы можете запрашивать вопросы по репортеру. Если вы встраиваете репортера в выпуск, вы можете запросить его только одним способом, репортер по выпускам.

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

1 голос
/ 04 марта 2010

Прелесть mongodb и других продуктов NoSQL в том, что нет никакой схемы для разработки. Я использую MongoDB и мне это нравится, мне не нужно писать SQL-запросы и ужасные JOIN-запросы! Итак, чтобы ответить на два ваших вопроса.

1 - Если вы создаете несколько документов, вам нужно сделать два вызова в БД. Не сказать, что это плохо, но если вы можете бросить все в один документ, почему бы и нет? Я помню, когда я использовал MySQL, я создавал таблицу «блог» и таблицу «комментарии». Теперь я добавляю комментарии к записи в той же коллекции (она же таблица) и продолжаю строить ее.

2 - Да ...

0 голосов
/ 09 мая 2013

Мне нравится MongoDB, но я должен сказать, что буду использовать его более трезво в следующем проекте.

В частности, мне не так повезло с функцией Embedded Document, как обещают люди.

Внедренный документ представляется полезным для композиции (см. UML Composition), но не для агрегирования. Листовые узлы - это хорошо, все, что находится в середине вашего графа объектов, не должно быть внедренным документом. Это сделает поиск и проверку ваших данных более трудной задачей, чем вы хотели бы.

Одна вещь, которая в MongoDB абсолютно лучше, это ваши отношения многие-к-X. Вы можете сделать «многие ко многим» только с двумя таблицами, и можно представить отношение «многие к одному» в любой таблице. То есть вы можете поместить 1 ключ в N рядов или N ключей в 1 ряд, или в оба. Примечательно, что запросы для выполнения операций над множествами (пересечение, объединение, дизъюнктное множество и т. Д.) На самом деле понятны вашим коллегам. Я никогда не был удовлетворен этими запросами в SQL. Мне часто приходится соглашаться на то, что «два других поймут это».

Если у вас когда-либо были большие данные, вы знаете, что вставки и обновления могут быть ограничены стоимостью индексов. Вам нужно меньше индексов в MongoDB; Индекс на A-B-C может использоваться для запроса A, A & B или A & B & C (но не B, C, B & C или A & C). Плюс возможность инвертировать отношения позволяет вам перемещать некоторые индексы во вторичные таблицы. Мои данные не стали достаточно большими, чтобы попробовать, но я надеюсь, что это поможет.

0 голосов
/ 03 мая 2013

Поначалу проектирование схемы в БД, ориентированных на документы, может показаться сложным, но при создании моего стартапа с использованием Symfony2 и MongoDB я обнаружил, что 80% времени аналогично реляционной БД.


Сначала подумайте, что это обычный дБ:

Для начала, просто создайте свою схему, как если бы вы использовали реляционную БД:

Каждый Entity должен иметь свои собственные Collection, , особенно если вам нужно разбить на страницы документы в нем .

(в Mongo можно несколько разбить на вложенные массивы документов, но возможности ограничены)


Затем просто удалите слишком сложную нормализацию:

  • мне нужна отдельная таблица категорий? (просто напишите категорию в столбце / свойстве в виде строки или встроенного документа)
  • Могу ли я хранить количество комментариев непосредственно как Int в коллекции Author? (затем обновить счет с событием, например, в Doctrine ODM)

Встроенные документы:

Использовать встроенные документы только для:

  • четкость (вложенные документы, такие как: addressInfo, billingInfo в коллекции пользователя)
  • для хранения тегов / категорий (например: [ name: "Sport", parent: "Hobby", page: "/sport" ])
  • для хранения простых нескольких значений (например, в User коллекции: список специальностей, список личных веб-сайтов)

Не используйте их, когда:

  • родительский документ станет слишком большим
  • когда вам нужно разбить их на страницы
  • когда вы чувствуете, что сущность достаточно важна, чтобы заслужить свою собственную коллекцию

Дублирующиеся значения в счетах сбора и предварительного вычисления:

Дублируйте некоторые значения столбцов / атрибутов из коллекции в другую, если вам нужно выполнить запрос для каждого значения в условиях where. (помните, что нет join с)

Например: в коллекции билетов укажите также имя автора (не только ID)

Также, если вам нужен счетчик (количество билетов, открытых пользователем, по категориям, ecc), предварительно рассчитайте их.


Вставить ссылки:

При наличии ссылки «один ко многим» или «многие ко многим» используйте встроенный массив со списком идентификаторов ссылочных документов (см. Ссылка на базу данных MongoDB ).

Вам нужно будет снова использовать событие, чтобы удалить идентификатор, если ссылочный документ будет удален. (Существует расширение для Doctrine ODM, если вы используете его: Ссылочная целостность )

Этот вид ссылок напрямую управляется Doctrine ODM: Reference Many


Его легко исправить ошибки:

Если вы поздно обнаружили, что допустили ошибку в дизайне схемы, достаточно просто исправить ее несколькими строчками Javascript для запуска непосредственно в консоли Mongo.

(хранимые процедуры упрощены: нет необходимости в сложных скриптах миграции)

Waring: не используйте Doctrine ODM Migrations , вы пожалеете об этом позже.

0 голосов
/ 03 мая 2013

Переделал этот ответ, поскольку исходный ответ неверно перевернул отношение из-за неправильного прочтения.

issue = {code: "asdf-11", заголовок: "asdf", репортер: {username: "qwer", роль: "manager"}}

Относительно того, является ли вложение какой-либо важной информации о пользователе (создателе) билета разумным решением или нет, зависит от особенностей системы.

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

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

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

Существует одна вопиющая проблема с этой схемой, которая заключается в дублировании изменения повторяющихся данных (нормализованные формы).

Давайте рассмотрим пример. Представьте, что вы столкнулись с реальной дилеммой, о которой я говорил ранее, и пользователь с именем Nigel хочет, чтобы его роль отныне отражала его новую должность. Это означает, что вам нужно обновить все строки, где Nigel является репортером, и изменить его role на эту новую позицию. Это может быть длительным и ресурсоемким запросом для MongoDB.

Снова противореча самому себе: если бы у вас было всего 100 билетов (что-то управляемое) на пользователя, тогда операция обновления, скорее всего, была бы не слишком плохой и, на самом деле, легко управляемой для базы данных; плюс из-за отсутствия перемещения (надеюсь) документов, это будет полностью обновленное место.

Так что, должно ли это быть встроено или нет, сильно зависит от ваших запросов, документов и т. Д., Однако, я бы сказал, что эта схема не очень хорошая идея; особенно из-за дублирования изменяющихся данных во многих корневых документах. Технически, да, вы могли бы сойти с рук, но я бы не стал пытаться.

Я бы вместо этого разделил их.

Если в документе есть объекты (поддокументы), могу ли я обновить их все в одном запросе?

Так же, как стиль отношений в моем первоначальном ответе, да и легко.

Например, давайте обновим роль с Nigel до MD (как уже упоминалось ранее) и изменим статус заявки на выполненный:

db.tickets.update({'reporter.username':'Nigel'},{$set:{'reporter.role':'MD', status: 'completed'}})

Таким образом, одна схема документа облегчает CRUD в этом случае.

Следует отметить, что, исходя из вашего английского, вы не можете использовать позиционный оператор для обновления всех вложенных документов в корневом документе. Вместо этого он обновит только первый найденный.

Опять же, надеюсь, это имеет смысл, и я ничего не упустил. НТН


Оригинальный ответ

здесь у меня есть пользователь, связанный с проблемой). Должен ли я создать другой документ «пользователь» и ссылаться на него в «выпускном» документе по его идентификатору (как в реляционных базах данных), или я должен оставить все данные пользователя в поддокументе?

Это серьезный вопрос, и для его продолжения требуются некоторые базовые знания.

Первое, что нужно учитывать, это размер проблемы:

issue = {code:"asdf-11", title:"asdf", reporter:{username:"qwer", role:"manager"}}

не очень большой, и, поскольку вам больше не нужна информация reporter (которая будет в корневом документе), она может быть меньше, однако проблемы никогда не бывают такими простыми. Если вы посмотрите на MongoDB JIRA, например: https://jira.mongodb.org/browse/SERVER-9548 (как случайная страница, подтверждающая мою точку зрения), то содержание «тикета» может быть весьма значительным.

Единственный способ получить реальную выгоду от встраивания билетов - это если вы сможете хранить ВСЕ пользовательскую информацию в одном 16-мегабайтном блоке непрерывного хранения, который является максимальным размером документа BSON (как установлено * 1062). * В настоящее время).

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

Даже если вы захотите сжать билет, может быть, до кода, заголовка и описания, вы все равно можете страдать от проблемы «швейцарского сыра», вызванной регулярными обновлениями и изменениями в документах в MongoDB, как всегда: http://www.10gen.com/presentations/storage-engine-internals хорошая ссылка на то, что я имею в виду.

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

Конечно, вы можете немного исправить эту проблему, используя степень распределения 2 размеров: http://docs.mongodb.org/manual/reference/command/collMod/#usePowerOf2Sizes, которая будет делать то же, что и на банке.

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

Если в документе есть объекты (поддокументы), могу ли я обновить их все в одном запросе?

Да, довольно легко. Это одна вещь, которая становится легче с встраиванием. Вы можете использовать запрос как:

db.users.update({user_id:uid,'tickets.code':'asdf-1'}, {$set:{'tickets.$.title':'Oh NOES'}})

Однако следует отметить, что вы можете обновлять только ОДИН поддокумент за один раз, используя позиционный оператор. Таким образом, это означает, что вы не можете в одной атомарной операции обновить все даты заявок на одного пользователя до 5 дней в будущем.

Что касается добавления нового билета, это довольно просто:

db.users.update({user_id:uid},{$push:{tickets:{code:asdf-1,title:"Whoop"}}})

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

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

...