MongoDB-Beziehungen: einbetten oder referenzieren?

524

Ich bin neu in MongoDB und komme aus einer relationalen Datenbank. Ich möchte eine Fragenstruktur mit einigen Kommentaren entwerfen, weiß aber nicht, welche Beziehung für Kommentare verwendet werden soll: embedoder reference?

Eine Frage mit einigen Kommentaren, wie z. B. Stackoverflow , hätte eine folgende Struktur:

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

Zuerst möchte ich eingebettete Kommentare verwenden (ich denke, dies embedwird in MongoDB empfohlen), wie folgt:

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

Es ist klar, aber ich mache mir Sorgen um diesen Fall: Wenn ich einen bestimmten Kommentar bearbeiten möchte, wie erhalte ich seinen Inhalt und seine Frage? Es gibt keine _idMöglichkeit, mich einen finden question_refzu lassen oder seine Frage zu finden. (Ich bin so ein Neuling, dass ich nicht weiß, ob es eine Möglichkeit gibt, dies ohne _idund zu tun question_ref.)

Muss ich refnicht verwenden embed? Dann muss ich eine neue Sammlung für Kommentare erstellen?

Freilauf
quelle
Alle Mongo-Objekte werden mit einer _ID erstellt, unabhängig davon, ob Sie das Feld erstellen oder nicht. Technisch gesehen hat also jeder Kommentar noch eine ID.
Robbie Guilfoyle
25
@ RobbieGuilfoyle nicht wahr - siehe stackoverflow.com/a/11263912/347455
pennstatephil
13
Ich stehe korrigiert, danke @pennstatephil :)
Robbie Guilfoyle
4
Was er vielleicht meint ist, dass alle Mungo- Objekte mit einer _id für diejenigen erstellt werden, die dieses Framework verwenden - siehe Mungo-Subdocs
Luca Steeb
1
Ein sehr gutes Buch zum Erlernen von Mongo-DB-Beziehungen ist "MongoDB Applied Design Patterns - O'Reilly". Kapitel eins, über diese Entscheidung sprechen, einbetten oder referenzieren?
Felipe Toledo

Antworten:

769

Dies ist mehr eine Kunst als eine Wissenschaft. Die Mongo-Dokumentation zu Schemata ist eine gute Referenz, aber hier sind einige Dinge zu beachten:

  • Gib so viel wie möglich ein

    Die Freude an einer Dokumentendatenbank besteht darin, dass viele Verknüpfungen eliminiert werden. Ihr erster Instinkt sollte darin bestehen, so viel wie möglich in einem einzigen Dokument zu platzieren. Da MongoDB-Dokumente strukturiert sind und Sie innerhalb dieser Struktur effizient abfragen können (dies bedeutet, dass Sie den Teil des Dokuments übernehmen können, den Sie benötigen, sodass die Dokumentgröße Sie nicht sonderlich beunruhigen sollte), besteht keine unmittelbare Notwendigkeit, Daten wie zu normalisieren Sie würden in SQL. Insbesondere sollten alle Daten, die außer dem übergeordneten Dokument nicht nützlich sind, Teil desselben Dokuments sein.

  • Trennen Sie Daten, auf die von mehreren Stellen aus verwiesen werden kann, in eine eigene Sammlung.

    Dies ist weniger ein "Speicherplatz" -Problem als vielmehr ein "Datenkonsistenz" -Problem. Wenn sich viele Datensätze auf dieselben Daten beziehen, ist es effizienter und weniger fehleranfällig, einen einzelnen Datensatz zu aktualisieren und an anderen Stellen Verweise darauf zu behalten.

  • Überlegungen zur Dokumentgröße

    MongoDB legt eine Größenbeschränkung von 4 MB (16 MB mit 1,8 MB) für ein einzelnes Dokument fest. In einer Welt mit GB Daten klingt dies klein, aber es sind auch 30.000 Tweets oder 250 typische Stapelüberlaufantworten oder 20 Flimmerfotos. Auf der anderen Seite sind dies weit mehr Informationen, als man auf einer typischen Webseite gleichzeitig präsentieren möchte. Überlegen Sie zunächst, was Ihre Anfragen einfacher macht. In vielen Fällen besteht die Sorge um die Dokumentgröße in einer vorzeitigen Optimierung.

  • Komplexe Datenstrukturen:

    MongoDB kann beliebige tief verschachtelte Datenstrukturen speichern, diese jedoch nicht effizient durchsuchen. Wenn Ihre Daten einen Baum, eine Gesamtstruktur oder ein Diagramm bilden, müssen Sie jeden Knoten und seine Kanten effektiv in einem separaten Dokument speichern. (Beachten Sie, dass es Datenspeicher gibt, die speziell für diese Art von Daten entwickelt wurden und die Sie ebenfalls berücksichtigen sollten.)

    Es wurde auch darauf hingewiesen, dass es unmöglich ist, eine Teilmenge von Elementen in einem Dokument zurückzugeben. Wenn Sie einige Teile jedes Dokuments auswählen müssen, ist es einfacher, sie voneinander zu trennen.

  • Datenkonsistenz

    MongoDB macht einen Kompromiss zwischen Effizienz und Konsistenz. Die Regel lautet, dass Änderungen an einem einzelnen Dokument immer atomar sind, während Aktualisierungen an mehreren Dokumenten niemals als atomar angenommen werden sollten. Es gibt auch keine Möglichkeit, einen Datensatz auf dem Server zu "sperren" (Sie können dies in die Logik des Clients einbauen, indem Sie beispielsweise ein Feld "sperren" verwenden). Überlegen Sie beim Entwerfen Ihres Schemas, wie Sie Ihre Daten konsistent halten. Im Allgemeinen ist es umso besser, je mehr Sie in einem Dokument aufbewahren.

Für das, was Sie beschreiben, würde ich die Kommentare einbetten und jedem Kommentar ein ID-Feld mit einer ObjectID geben. In die ObjectID ist ein Zeitstempel eingebettet, sodass Sie diesen verwenden können, anstatt ihn zu erstellen, wenn Sie möchten.

John F. Miller
quelle
1
Ich möchte der OP-Frage hinzufügen: Mein Kommentarmodell enthält den Benutzernamen und den Link zu seinem Avatar. Was wäre der beste Ansatz, wenn man bedenkt, dass ein Benutzer seinen Namen / Avatar ändern kann?
user1102018
5
In Bezug auf 'Komplexe Datenstrukturen' scheint es möglich zu sein, eine Teilmenge von Elementen in einem Dokument mithilfe des Aggregationsframeworks zurückzugeben (versuchen Sie $ unwind).
Eyal Roth
4
Ähm, diese Technik war Anfang 2012 in MongoDB entweder nicht möglich oder nicht allgemein bekannt. Angesichts der Beliebtheit dieser Frage möchte ich Sie ermutigen, Ihre eigene aktualisierte Antwort zu schreiben. Ich fürchte, ich habe mich von der aktiven Entwicklung auf MongoDB entfernt und bin nicht in der Lage, Ihren Kommentar in meinem ursprünglichen Beitrag anzusprechen.
John F. Miller
54
16 MB = 30 Millionen Tweets? ths menas ungefähr 0,5 Byte pro Tweet?!
Paolo
8
Ja, es scheint, dass ich um den Faktor 1000 abwesend war und einige Leute finden das wichtig. Ich werde den Beitrag bearbeiten. WRT 560 Bytes pro Tweet, als ich dies 2011 redete, war Twitter immer noch an Textnachrichten und Ruby 1.4-Zeichenfolgen gebunden; Mit anderen Worten, nur noch ASCII-Zeichen.
John F. Miller
39

Im Allgemeinen ist die Einbettung gut, wenn Sie Eins-zu-Eins- oder Eins-zu-Viele-Beziehungen zwischen Entitäten haben, und die Referenz ist gut, wenn Sie Viele-zu-Viele-Beziehungen haben.

ywang1724
quelle
10
Können Sie bitte einen Referenzlink hinzufügen? Vielen Dank.
db80
Wie finden Sie einen bestimmten Kommentar mit diesem Design von einem bis vielen?
Mauricio Pastorini
29

Wenn ich einen bestimmten Kommentar bearbeiten möchte, wie erhalte ich seinen Inhalt und seine Frage?

Sie können nach Unterdokument abfragen : db.question.find({'comments.content' : 'xxx'}).

Dadurch wird das gesamte Fragendokument zurückgegeben. Um den angegebenen Kommentar zu bearbeiten, müssen Sie den Kommentar auf dem Client suchen, bearbeiten und in der Datenbank speichern.

Wenn Ihr Dokument ein Array von Objekten enthält, müssen diese Unterobjekte im Allgemeinen clientseitig geändert werden.

Gates VP
quelle
4
Dies funktioniert nicht, wenn zwei Kommentare identischen Inhalt haben. Man könnte argumentieren, dass wir der Suchabfrage auch einen Autor hinzufügen könnten, was immer noch nicht funktionieren würde, wenn der Autor zwei identische Kommentare mit demselben Inhalt machen würde
Steel Brain
@SteelBrain: Wenn er den Kommentarindex beibehalten hätte, könnte die Punktnotation hilfreich sein. siehe stackoverflow.com/a/33284416/1587329
serv-inc
13
Ich verstehe nicht, wie diese Antwort 34 positive Stimmen hat. Die zweiten mehreren Personen kommentieren dasselbe, was das gesamte System brechen würde. Dies ist ein absolut schreckliches Design und sollte niemals verwendet werden. Der Weg, den @user macht, ist der Weg zu gehen
user2073973
21

Nun, ich bin etwas spät dran, möchte aber trotzdem meine Art der Schemaerstellung teilen.

Ich habe Schemata für alles, was durch ein Wort beschrieben werden kann, wie Sie es in der klassischen OOP tun würden.

Z.B

  • Kommentar
  • Konto
  • Benutzer
  • Blogeintrag
  • ...

Jedes Schema kann als Dokument oder Unterdokument gespeichert werden, daher erkläre ich dies für jedes Schema.

Dokumentieren:

  • Kann als Referenz verwendet werden. (ZB hat der Benutzer einen Kommentar abgegeben -> Kommentar hat einen "made by" Verweis auf den Benutzer)
  • Ist ein "Root" in Ihrer Anwendung. (ZB der Blogpost -> es gibt eine Seite über den Blogpost)

Unterdokument:

  • Kann nur einmal verwendet werden / ist nie eine Referenz. (ZB Kommentar wird im Blogpost gespeichert)
  • Ist niemals ein "Root" in Ihrer Anwendung. (Der Kommentar wird nur auf der Blogpost-Seite angezeigt, aber auf der Seite geht es immer noch um den Blogpost.)
Silom
quelle
20

Ich bin auf diese kleine Präsentation gestoßen, als ich diese Frage selbst recherchiert habe. Ich war überrascht, wie gut es angelegt war, sowohl die Informationen als auch die Präsentation.

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

Es fasste zusammen:

Wenn Sie viele [untergeordnete Dokumente] haben oder wenn diese groß sind, ist in der Regel eine separate Sammlung am besten geeignet.

Kleinere und / oder weniger Dokumente eignen sich normalerweise zum Einbetten.

Chris Bloom
quelle
11
Wie viel ist a lot? 3? 10? 100? Was ist large? 1kb? 1 MB? 3 Felder? 20 Felder? Was ist smaller/ fewer?
Traxo
1
Das ist eine gute Frage, auf die ich keine konkrete Antwort habe. Dieselbe Präsentation enthielt eine Folie mit der Aufschrift "Ein Dokument, einschließlich aller eingebetteten Dokumente und Arrays, darf 16 MB nicht überschreiten". Dies könnte Ihr Cutoff sein oder einfach zu dem passen, was für Ihre spezifische Situation vernünftig / bequem erscheint. In meinem aktuellen Projekt sind die meisten eingebetteten Dokumente für 1: 1-Beziehungen oder 1: viele, bei denen die eingebetteten Dokumente wirklich einfach sind.
Chris Bloom
Siehe auch den aktuellen Top-Kommentar von @ john-f-miller, der zwar keine spezifischen Zahlen für einen Schwellenwert enthält, aber einige zusätzliche Hinweise enthält, die Ihnen bei Ihrer Entscheidung helfen sollen.
Chris Bloom
16

Ich weiß, dass dies ziemlich alt ist, aber wenn Sie nach einer Antwort auf die Frage des OP suchen, wie nur ein bestimmter Kommentar zurückgegeben werden kann, können Sie den Operator $ (Abfrage) wie folgt verwenden:

db.question.update({'comments.content': 'xxx'}, {'comments.$': true})
Finspin
quelle
4
Dies funktioniert nicht, wenn zwei Kommentare identischen Inhalt haben. Man könnte argumentieren, dass wir der Suchabfrage auch einen Autor hinzufügen könnten, was immer noch nicht funktionieren würde, wenn der Autor zwei identische Kommentare mit demselben Inhalt machen würde
Steel Brain
1
@ SteelBrain: Gut gespielt, Sir, gut gespielt.
JakeStrang
12

Ja, wir können die Referenz im Dokument verwenden. Um das andere Dokument genau wie SQL zu füllen, füge ich hinzu. In Mongo DB haben sie keine Verknüpfungen, um ein Dokument mit mehreren Beziehungen zuzuordnen. Stattdessen können wir das Auffüllen verwenden , um unser Szenario zu erfüllen.

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

Beim Auffüllen werden die angegebenen Pfade im Dokument automatisch durch Dokumente aus anderen Sammlungen ersetzt. Wir können ein einzelnes Dokument, mehrere Dokumente, ein einfaches Objekt, mehrere einfache Objekte oder alle von einer Abfrage zurückgegebenen Objekte füllen. Schauen wir uns einige Beispiele an.

Weitere Informationen erhalten Sie unter: http://mongoosejs.com/docs/populate.html

Narendran
quelle
5
Mongoose gibt für jedes ausgefüllte Feld eine separate Anfrage aus. Dies unterscheidet sich von SQL JOINS, da diese auf dem Server ausgeführt werden. Dies beinhaltet zusätzlichen Datenverkehr zwischen dem App-Server und dem Mongodb-Server. Auch dies können Sie bei der Optimierung berücksichtigen. Trotzdem ist Ihre Antwort immer noch korrekt.
Max
6

Eigentlich bin ich ziemlich neugierig, warum niemand über die UML-Spezifikationen gesprochen hat. Als Faustregel gilt: Wenn Sie eine Aggregation haben, sollten Sie Referenzen verwenden. Wenn es sich jedoch um eine Komposition handelt, ist die Kopplung stärker und Sie sollten eingebettete Dokumente verwenden.

Und Sie werden schnell verstehen, warum es logisch ist. Wenn ein Objekt unabhängig vom übergeordneten Objekt vorhanden sein kann, möchten Sie auch dann darauf zugreifen, wenn das übergeordnete Objekt nicht vorhanden ist. Da Sie es einfach nicht in ein nicht vorhandenes übergeordnetes Element einbetten können, müssen Sie es in seiner eigenen Datenstruktur live schalten. Wenn ein übergeordnetes Element vorhanden ist, verknüpfen Sie es einfach miteinander, indem Sie dem übergeordneten Objekt eine Referenz des Objekts hinzufügen.

Sie wissen nicht wirklich, was der Unterschied zwischen den beiden Beziehungen ist? Hier ist ein Link, der sie erklärt: Aggregation vs Komposition in UML

Bonjour123
quelle
Warum -1? Bitte geben Sie eine Erklärung, die den Grund klären würde
Bonjour123
1

Wie erhalte ich den Inhalt und die Frage, wenn ich einen bestimmten Kommentar bearbeiten möchte?

Wenn Sie die Anzahl der Kommentare und den Index des Kommentars, den Sie ändern möchten, nachverfolgt haben, können Sie den Punktoperator verwenden ( SO-Beispiel) ).

Sie könnten f.ex.

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

(als eine andere Möglichkeit, die Kommentare in der Frage zu bearbeiten)

serv-inc
quelle