Relacje MongoDB: osadzanie czy odwołanie?

524

Jestem nowy w MongoDB - pochodzę z relacyjnej bazy danych. Chcę zaprojektować strukturę pytań z niektórymi komentarzami, ale nie wiem, jakiej relacji użyć w przypadku komentarzy: embedlub reference?

Pytanie z niektórymi komentarzami, takie jak stackoverflow , miałoby następującą strukturę:

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

Najpierw chcę użyć osadzonych komentarzy (myślę, że embedjest to zalecane w MongoDB), w następujący sposób:

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

To jasne, ale martwię się o tę sprawę: jeśli chcę edytować określony komentarz, w jaki sposób mogę uzyskać jego treść i pytanie? Nie ma nic, _idco pozwoliłoby mi znaleźć, ani question_refpozwolić mi znaleźć jego pytanie. (Jestem tak początkujący, że nie wiem, czy można to zrobić bez _idi question_ref.)

Czy muszę używać refnie embed? Więc muszę utworzyć nową kolekcję dla komentarzy?

Freewind
źródło
Wszystkie obiekty Mongo są tworzone z identyfikatorem _ID, niezależnie od tego, czy utworzysz pole, czy nie. Więc technicznie każdy komentarz będzie nadal miał identyfikator.
Robbie Guilfoyle
25
@RobbieGuilfoyle nieprawda - patrz stackoverflow.com/a/11263912/347455
pennstatephil
13
Stoję poprawiony, dzięki @pennstatephil :)
Robbie Guilfoyle
4
To, co on może mieć na myśli, to to, że wszystkie obiekty mangusty są tworzone z identyfikatorem _id dla tych, którzy korzystają z tego frameworka - patrz podrozdziały mangusty
Luca Steeb
1
Bardzo dobrą książką do nauki relacji między bazami danych mongo jest „MongoDB Applied Design Patterns - O'Reilly”. Rozdział pierwszy, porozmawiaj o tej decyzji, aby ją osadzić lub odwołać?
Felipe Toledo

Odpowiedzi:

769

To bardziej sztuka niż nauka. Mongo Dokumentacja schematów jest odniesienie dobra, ale jest kilka rzeczy do rozważenia:

  • Wrzuć jak najwięcej

    Radość z bazy dokumentów polega na tym, że eliminuje ona wiele połączeń. Twoim pierwszym instynktem powinno być umieszczenie jak największej ilości w jednym dokumencie. Ponieważ dokumenty MongoDB mają strukturę i ponieważ możesz efektywnie wyszukiwać w obrębie tej struktury (oznacza to, że możesz wziąć część dokumentu, której potrzebujesz, więc rozmiar dokumentu nie powinien cię bardzo martwić), nie ma natychmiastowej potrzeby normalizacji danych, takich jak zrobiłbyś w SQL. W szczególności wszelkie dane, które nie są przydatne poza dokumentem nadrzędnym, powinny stanowić część tego samego dokumentu.

  • Oddzielne dane, do których można odwoływać się z wielu miejsc, do własnej kolekcji.

    To nie tyle problem „przestrzeni dyskowej”, ile problem „spójności danych”. Jeśli wiele rekordów odnosi się do tych samych danych, bardziej wydajna i mniej podatna na błędy jest aktualizacja jednego rekordu i przechowywanie odniesień do niego w innych miejscach.

  • Uwagi dotyczące rozmiaru dokumentu

    MongoDB nakłada limit rozmiaru 4 MB (16 MB z 1.8) na pojedynczy dokument. W świecie GB danych brzmi to niewielko, ale jest to również 30 tysięcy tweetów lub 250 typowych odpowiedzi na przepełnienie stosu lub 20 migotliwych zdjęć. Z drugiej strony jest to o wiele więcej informacji, niż można by jednorazowo przedstawić na typowej stronie internetowej. Najpierw zastanów się, co ułatwi twoje zapytania. W wielu przypadkach obawy dotyczące rozmiarów dokumentów będą przedwczesną optymalizacją.

  • Złożone struktury danych:

    MongoDB może przechowywać dowolne głęboko zagnieżdżone struktury danych, ale nie może ich skutecznie wyszukiwać. Jeśli Twoje dane tworzą drzewo, las lub wykres, musisz skutecznie przechowywać każdy węzeł i jego krawędzie w osobnym dokumencie. (Należy pamiętać, że istnieją magazyny danych zaprojektowane specjalnie dla tego typu danych, które również należy wziąć pod uwagę)

    Wskazano również , że nie można zwrócić podzbioru elementów w dokumencie. Jeśli musisz wybrać i wybrać kilka bitów każdego dokumentu, łatwiej będzie je rozdzielić.

  • Spójność danych

    MongoDB stanowi kompromis między wydajnością a konsekwencją. Zasadą jest, że zmiany w jednym dokumencie są zawsze atomowe, a aktualizacji wielu dokumentów nigdy nie należy zakładać, że są atomowe. Nie ma także sposobu na „zablokowanie” rekordu na serwerze (można go wbudować w logikę klienta, używając na przykład pola „zablokuj”). Projektując schemat, zastanów się, jak zachować spójność danych. Zasadniczo im więcej trzymasz w dokumencie, tym lepiej.

Dla tego, co opisujesz, chciałbym osadzić komentarze i nadać każdemu komentarzowi pole identyfikatora o identyfikatorze obiektu. ObjectID ma osadzony znacznik czasu, więc możesz go użyć zamiast go utworzyć, jeśli chcesz.

John F. Miller
źródło
1
Chciałbym dodać do pytania OP: Mój model komentarzy zawiera nazwę użytkownika i link do jego awatara. Jakie byłoby najlepsze podejście, biorąc pod uwagę, że użytkownik może zmodyfikować swoje imię / awatar?
user1102018
5
Jeśli chodzi o „Złożone struktury danych”, wydaje się, że możliwe jest zwrócenie podzbioru elementów w dokumencie za pomocą struktury agregacji (spróbuj $ odwiń).
Eyal Roth,
4
BŁĄD, Ta technika była albo niemożliwa, albo mało znana w MongoDB na początku 2012 roku. Biorąc pod uwagę popularność tego pytania, zachęcam do napisania własnej zaktualizowanej odpowiedzi. Obawiam się, że zrezygnowałem z aktywnego rozwoju MongoDB i nie jestem w stanie zająć się komentarzem w moim oryginalnym poście.
John F. Miller
54
16 MB = 30 milionów tweetów? co około 0,5 bajta na tweet ?!
Paolo,
8
Tak, wygląda na to, że byłem wyłączony 1000 razy i niektórym ludziom to się podoba. Zmienię post. WRT 560 bajtów na tweet, kiedy piszę to w 2011 roku, Twitter wciąż był powiązany z wiadomościami tekstowymi i ciągami Ruby 1.4; innymi słowy nadal tylko znaki ASCII.
John F. Miller,
39

Zasadniczo osadzanie jest dobre, jeśli masz relacje jeden-do-jednego lub jeden-do-wielu między jednostkami, a odniesienie jest dobre, jeśli masz relacje wiele-do-wielu.

ywang1724
źródło
10
czy możesz dodać link referencyjny? Dzięki.
db80
Jak znaleźć konkretny komentarz z tym projektem od jednego do wielu?
Mauricio Pastorini,
29

Jeśli chcę edytować określony komentarz, jak uzyskać jego treść i pytanie?

Można wyszukać przez sub-dokumentu: db.question.find({'comments.content' : 'xxx'}).

Spowoduje to zwrócenie całego dokumentu pytania. Aby edytować określony komentarz, musisz znaleźć komentarz na kliencie, dokonać edycji i zapisać go z powrotem w bazie danych.

Ogólnie, jeśli twój dokument zawiera tablicę obiektów, przekonasz się, że te pod-obiekty będą musiały zostać zmodyfikowane po stronie klienta.

Gates VP
źródło
4
to nie zadziała, jeśli dwa komentarze mają identyczną treść. można argumentować, że możemy również dodać autora do zapytania wyszukiwania, co nadal nie działałoby, gdyby autor napisał dwa identyczne komentarze o tej samej treści
Steel Brain
@ SteelBrain: jeśli zachowałby indeks komentarzy, pomocna byłaby notacja kropkowa. patrz stackoverflow.com/a/33284416/1587329
serv-inc
13
Nie rozumiem, w jaki sposób ta odpowiedź ma 34 głosy poparcia, druga wiele osób komentuje to samo, co złamałby cały system. Jest to absolutnie okropny projekt i nigdy nie należy go używać. Sposób, w jaki @user to robi, jest właściwy
user2073973
21

Cóż, jestem trochę spóźniony, ale nadal chciałbym podzielić się moim sposobem tworzenia schematu.

Mam schematy wszystkiego, co można opisać słowem, tak jak zrobiłbyś to w klasycznym OOP.

NA PRZYKŁAD

  • Komentarz
  • Konto
  • Użytkownik
  • Post na blogu
  • ...

Każdy schemat można zapisać jako dokument lub dokument podrzędny, dlatego deklaruję to dla każdego schematu.

Dokument:

  • Może być stosowany jako odniesienie. (Np. Użytkownik skomentował -> komentarz ma odniesienie „wykonane przez” do użytkownika)
  • Jest „rootem” w twojej aplikacji. (Np. Blog -> jest strona o blogu)

Poddokument:

  • Można go użyć tylko raz / nigdy nie stanowi odniesienia. (Np. Komentarz jest zapisywany w blogu)
  • Nigdy nie jest „rootem” w twojej aplikacji. (Komentarz pojawia się tylko na stronie blogu, ale strona nadal dotyczy blogu)
Silom
źródło
20

Natknąłem się na tę małą prezentację podczas samodzielnego badania tego pytania. Byłem zaskoczony, jak dobrze zostało to ułożone, zarówno informacje, jak i prezentacja.

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

Podsumował:

Zasadniczo, jeśli masz dużo [dokumentów podrzędnych] lub jeśli są one duże, oddzielna kolekcja może być najlepsza.

Mniejsze i / lub mniej dokumentów zwykle nadają się do osadzania.

Chris Bloom
źródło
11
Ile to jest a lot? 3? 10? 100? Co jest large? 1kb? 1 MB? 3 pola? 20 pól? Co to jest smaller/ fewer?
Traxo
1
To dobre pytanie, na które nie mam konkretnej odpowiedzi. Ta sama prezentacja zawierała slajd z napisem „Dokument, w tym wszystkie osadzone dokumenty i tablice, nie może przekroczyć 16 MB”, więc może to być Twoja wartość graniczna lub po prostu iść z tym, co wydaje się rozsądne / wygodne w konkretnej sytuacji. W moim obecnym projekcie większość osadzonych dokumentów dotyczy relacji 1: 1 lub 1: wielu, w których osadzone dokumenty są naprawdę proste.
Chris Bloom
Zobacz także aktualny najwyższy komentarz autorstwa @ john-f-miller, który nie podając również konkretnych wartości progowych, zawiera dodatkowe wskazówki, które powinny pomóc w podjęciu decyzji.
Chris Bloom
16

Wiem, że jest to dość stare, ale jeśli szukasz odpowiedzi na pytanie PO dotyczące zwrotu tylko określonego komentarza, możesz użyć operatora $ (zapytanie) w następujący sposób:

db.question.update({'comments.content': 'xxx'}, {'comments.$': true})
finspin
źródło
4
to nie zadziała, jeśli dwa komentarze mają identyczną treść. można argumentować, że możemy również dodać autora do zapytania wyszukiwania, co nadal nie działałoby, gdyby autor napisał dwa identyczne komentarze o tej samej treści
Steel Brain
1
@ SteelBrain: Dobrze gra pan, dobrze gra.
JakeStrang,
12

Tak, możemy użyć odwołania w dokumencie. Aby zapełnić inny dokument tak jak sql i joins. W mongo db nie mają złączeń do mapowania jednego do wielu dokumentów relacji. Zamiast tego możemy użyć wypełnienia, aby spełnić nasz scenariusz.

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' }]
});

Populacja to proces automatycznego zastępowania określonych ścieżek w dokumencie dokumentem (dokumentami) z innych kolekcji. Możemy wypełnić jeden dokument, wiele dokumentów, zwykły obiekt, wiele zwykłych obiektów lub wszystkie obiekty zwrócone z zapytania. Spójrzmy na kilka przykładów.

Lepiej możesz uzyskać więcej informacji na stronie: http://mongoosejs.com/docs/populate.html

Narendran
źródło
5
Mongoose wyda osobne żądanie dla każdego zapełnionego pola. Różni się to od JOINS SQL, ponieważ są one wykonywane na serwerze. Obejmuje to dodatkowy ruch między serwerem aplikacji a serwerem mongodb. Ponownie możesz wziąć to pod uwagę podczas optymalizacji. Niemniej jednak Twoja odpowiedź jest nadal poprawna.
Maks.
6

Właściwie jestem ciekawy, dlaczego nikt nie mówił o specyfikacjach UML. Ogólna zasada jest taka, że ​​jeśli masz agregację, powinieneś użyć referencji. Ale jeśli jest to kompozycja, połączenie jest silniejsze i powinieneś używać osadzonych dokumentów.

I szybko zrozumiesz, dlaczego jest to logiczne. Jeśli obiekt może istnieć niezależnie od rodzica, będziesz chciał uzyskać do niego dostęp, nawet jeśli rodzic nie istnieje. Ponieważ po prostu nie możesz osadzić go w nieistniejącym obiekcie nadrzędnym, musisz włączyć go do własnej struktury danych. A jeśli rodzic istnieje, po prostu połącz je ze sobą, dodając odwołanie do obiektu w rodzicu.

Naprawdę nie wiem, jaka jest różnica między tymi dwoma związkami? Oto link wyjaśniający je: Agregacja vs Kompozycja w UML

Bonjour123
źródło
Dlaczego -1? Podaj wyjaśnienie, które wyjaśni przyczynę
Bonjour123
1

Jeśli chcę edytować określony komentarz, w jaki sposób mogę uzyskać jego treść i pytanie?

Jeśli śledziłeś liczbę komentarzy i indeks komentarza, który chcesz zmienić, możesz użyć operatora kropki ( przykład SO ).

Mógłbyś zrobić np.

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

(jako inny sposób edycji komentarzy w pytaniu)

serv-inc
źródło