Отношения в документно-ориентированной базе данных?
Я заинтересован в документно-ориентированных базах данных и хотел бы поиграть с MongoDB. Поэтому я запустил довольно простой проект (средство отслеживания проблем), но мне трудно думать нереляционным образом.
Мои проблемы:
У меня есть два объекта, которые связаны друг с другом (например,
issue = {code:"asdf-11", title:"asdf", reporter:{username:"qwer", role:"manager"}}
- здесь у меня есть пользователь, связанный с вопросом). Должен ли я создать другой документ "пользователь" и ссылаться на него в "выпускном" документе по его идентификатору (как в реляционных базах данных), или я должен оставить все данные пользователя в поддокументе?Если у меня есть объекты (вложенные документы) в документе, могу ли я обновить их все в одном запросе?
6 ответов
Я совершенно новичок в документно-ориентированных базах данных, и сейчас я пытаюсь разработать своего рода CMS с использованием node.js и mongodb, поэтому я сталкиваюсь с теми же проблемами, что и вы.
Методом проб и ошибок я нашел это практическое правило: я делаю коллекцию для каждой сущности, которая может быть "предметом" для моих запросов, в то же время встраивая остальное в другие объекты.
Например, комментарии в записи блога могут быть встроены, потому что обычно они связаны с самой записью, и я не могу думать о полезном запросе, сделанном глобально для всех комментариев. С другой стороны, теги, прикрепленные к сообщению, могут заслуживать отдельной коллекции, потому что даже если они привязаны к сообщению, вам может потребоваться глобально рассуждать обо всех тегах (например, составляя список популярных тем).
На мой взгляд, это на самом деле довольно просто. Доступ к встроенным документам возможен только через их основной документ. Если вы можете представить необходимость запроса объекта вне контекста главного документа, не встраивайте его. Используйте ссылку
Для вашего примера
issue = {code:"asdf-11", title:"asdf", reporter:{username:"qwer", role:"manager"}}
Я сделал бы выпуск и репортер каждый их собственный документ, и ссылался на репортера в проблеме. Вы также можете сослаться на список проблем в репортере. Таким образом, вы не будете дублировать репортеров в вопросах, вы можете запрашивать их у каждого по отдельности, вы можете запрашивать репортера по проблеме, и вы можете запрашивать вопросы по репортеру. Если вы встраиваете репортера в выпуск, вы можете запросить только один способ, репортер по выпускам.
Если вы встраиваете документы, вы можете обновить их все в одном запросе, но вам нужно повторить обновление в каждом основном документе. Это еще одна веская причина использовать справочные документы.
Прелесть mongodb и других продуктов NoSQL в том, что нет никакой схемы для разработки. Я использую MongoDB и мне это нравится, мне не нужно писать SQL-запросы и ужасные JOIN-запросы! Итак, чтобы ответить на ваши два вопроса.
1 - Если вы создаете несколько документов, вам нужно сделать два вызова в БД. Не сказать, что это плохо, но если вы можете бросить все в один документ, почему бы и нет? Я помню, когда я использовал MySQL, я создавал таблицу "блог" и таблицу "комментарии". Теперь я добавляю комментарии к записи в той же коллекции (она же таблица) и продолжаю строить ее.
2 - Да...
Поначалу проектирование схемы в документно-ориентированных БД может показаться сложным, но при создании моего стартапа с использованием Symfony2 и MongoDB я обнаружил, что 80% времени аналогично реляционной БД.
Во-первых, подумайте, как нормальный дБ:
Для начала просто создайте свою схему, как если бы вы использовали реляционную базу данных:
каждый Entity
должен иметь свой собственный Collection
особенно если вам нужно разбить документы на нем.
(в Mongo вы можете несколько разбивать вложенные массивы документов, но возможности ограничены)
Затем просто удалите слишком сложную нормализацию:
- мне нужна отдельная таблица категорий? (просто напишите категорию в столбце / свойстве в виде строки или встроенного документа)
- Могу ли я хранить количество комментариев непосредственно как Int в коллекции Author? (затем обновите счет с помощью события, например, в Doctrine ODM)
Встроенные документы:
Используйте встроенные документы только для:
- четкость (вложенные документы, такие как:
addressInfo
,billingInfo
в коллекции пользователя) - хранить теги / категории (например:
[ name: "Sport", parent: "Hobby", page: "/sport" ]
) - хранить простые множественные значения (например, в
User
Коллекция: список специальностей, список персональных сайтов)
Не используйте их, когда:
- родительский документ станет слишком большим
- когда вам нужно разбить их на страницы
- когда вы чувствуете, что сущность достаточно важна, чтобы заслужить свою собственную коллекцию
Дублирующиеся значения в счетах сбора и предварительного вычисления:
Дублируйте некоторые значения столбцов / атрибутов из коллекции в другую, если вам нужно выполнить запрос для каждого значения в условиях where. (помните, что нет join
s)
Например: в коллекции билетов укажите также имя автора (не только ID
)
Также, если вам нужен счетчик (количество заявок, открытых пользователем, по категориям, ecc), предварительно рассчитайте их.
Вставить ссылки:
Если у вас есть ссылка "один ко многим" или "многие ко многим", используйте встроенный массив со списком идентификаторов ссылочных документов (см. Ссылку на базу данных MongoDB).
Вам нужно будет снова использовать событие для удаления идентификатора, если ссылочный документ будет удален. (Существует расширение для Doctrine ODM, если вы используете его: ссылочная целостность)
Этот вид ссылок напрямую управляется Doctrine ODM: многие ссылки
Его легко исправить ошибки:
Если вы поздно обнаружили, что допустили ошибку в дизайне схемы, достаточно просто исправить ее несколькими строчками Javascript для запуска непосредственно в консоли Mongo.
(хранимые процедуры упрощены: нет необходимости в сложных скриптах миграции)
Waring: не используйте Doctrine ODM Migrations, вы пожалеете об этом позже.
Мне нравится MongoDB, но я должен сказать, что буду использовать его более трезво в следующем проекте.
В частности, мне не так повезло с функцией Embedded Document, как обещают люди.
Внедренный документ представляется полезным для композиции (см. UML Composition), но не для агрегирования. Листовые узлы - это хорошо, все, что находится в середине графа вашего объекта, не должно быть встроенным документом. Это сделает поиск и проверку ваших данных более трудным, чем вы хотели бы.
Одна вещь, которая является абсолютно лучшей в MongoDB, это ваши отношения многие-к-X. Вы можете сделать "многие ко многим" только с двумя таблицами, и можно представить отношение "многие к одному" в любой таблице. То есть вы можете поместить 1 ключ в N рядов или N ключей в 1 ряд, или в оба. Примечательно, что запросы для выполнения операций над множествами (пересечение, объединение, непересекающееся множество и т. Д.) На самом деле понятны вашим коллегам. Я никогда не был удовлетворен этими запросами в SQL. Мне часто приходится соглашаться на то, что "два других поймут это".
Если у вас когда-либо были большие данные, вы знаете, что вставки и обновления могут быть ограничены стоимостью индексов. Вам нужно меньше индексов в MongoDB; индекс для ABC может использоваться для запроса A, A & B или A & B & C (но не B, C, B & C или A & C). Плюс возможность инвертировать отношения позволяет перемещать некоторые индексы во вторичные таблицы. Мои данные не стали достаточно большими, чтобы попробовать, но я надеюсь, что это поможет.
Переделал этот ответ, так как исходный ответ неверно перевернул отношение из-за неправильного прочтения.
проблема = {код:"asdf-11", заголовок:"asdf", репортер:{имя пользователя:"qwer", роль: "менеджер"}}
Относительно того, является ли вложение какой-либо важной информации о пользователе (создателе) билета разумным решением, зависит от особенностей системы.
Предоставляете ли вы этим пользователям возможность войти в систему и сообщить о найденных проблемах? Если это так, то, вероятно, вы захотите включить это отношение в коллекцию пользователей.
С другой стороны, если это не так, то вы можете легко избежать этой схемы. Единственная проблема, которую я вижу здесь, это то, что если вы хотите связаться с репортером, а его роль в работе изменилась, это несколько неловко; однако это реальная дилемма, а не проблема базы данных.
Поскольку поддокумент представляет собой однозначное отношение к репортеру, вы также не должны испытывать проблем фрагментации, упомянутых в моем первоначальном ответе.
Существует одна вопиющая проблема с этой схемой, которая заключается в дублировании изменения повторяющихся данных (нормализованные формы).
Давайте возьмем пример. Представьте, что вы столкнулись с реальной дилеммой, о которой я говорил ранее, и пользователь по имени 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
информация (которая будет в корневом документе) может быть меньше, однако проблемы никогда не бывают такими простыми. Если вы посмотрите на JIRA MongoDB, например: https://jira.mongodb.org/browse/SERVER-9548 (как случайная страница, подтверждающая мою точку зрения), то содержание "тикета" может быть весьма значительным.
Единственный способ получить реальную выгоду от встраивания билетов - это если вы сможете хранить ВСЕ пользовательскую информацию в одном 16-мегабайтном блоке непрерывного хранения, который является максимальным размером документа BSON (в соответствии с mongod
В настоящее время).
Я не думаю, что вы сможете хранить все билеты под одним пользователем.
Даже если бы вам пришлось сократить билет, возможно, код, заголовок и описание, вы все равно могли бы страдать от проблемы "швейцарского сыра", вызванной регулярными обновлениями и изменениями в документах в MongoDB, как всегда это: http://www.10gen.com/presentations/storage-engine-internals является хорошим справочным материалом для того, что я имею в виду.
Обычно вы становитесь свидетелями этой проблемы, когда пользователи добавляют несколько заявок в свой корневой пользовательский документ. Сами билеты тоже будут меняться, но, возможно, не радикально или часто.
Конечно, вы можете немного исправить эту проблему, используя распределение мощностей двух размеров: http://docs.mongodb.org/manual/reference/command/collMod/, которые будут делать именно то, что написано на банке.
Хорошо, гипотетически, если бы вы имели только 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"}}})
Так что да, вы можете довольно просто, в зависимости от ваших запросов, обновить данные всего пользователя за один вызов.
Это был довольно длинный ответ, так что, надеюсь, я ничего не пропустил, надеюсь, это поможет.