Relations MongoDB: intégration ou référence?

524

Je suis nouveau sur MongoDB - issu d'une base de données relationnelle. Je veux concevoir une structure de questions avec quelques commentaires, mais je ne sais pas quelle relation utiliser pour les commentaires: embedou reference?

Une question avec quelques commentaires, comme stackoverflow , aurait une structure comme celle-ci:

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

Au début, je veux utiliser des commentaires intégrés (je pense que embedc'est recommandé dans MongoDB), comme ceci:

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

C'est clair, mais je suis inquiet dans ce cas: si je veux éditer un commentaire spécifié, comment puis-je obtenir son contenu et sa question? Il n'y a rien _idà me laisser en trouver un, ni question_refà me laisser trouver sa question. (Je suis tellement novice, que je ne sais pas s'il y a moyen de le faire sans _idet question_ref.)

Dois-je utiliser refnon embed? Ensuite, je dois créer une nouvelle collection de commentaires?

Freewind
la source
Tous les objets Mongo sont créés avec un _ID, que vous créiez le champ ou non. Donc, techniquement, chaque commentaire aura toujours un identifiant.
Robbie Guilfoyle
25
@RobbieGuilfoyle pas vrai - voir stackoverflow.com/a/11263912/347455
pennstatephil
14
Je suis corrigé, merci @pennstatephil :)
Robbie Guilfoyle
4
Ce qu'il veut peut-être dire, c'est que tous les objets mangouste sont créés avec un _id pour ceux qui utilisent ce framework - voir les sous-documents mangouste
Luca Steeb
1
Un très bon livre pour apprendre les relations mongo db est "MongoDB Applied Design Patterns - O'Reilly". Chapitre un, parler de cette décision, intégrer ou référencer?
Felipe Toledo

Réponses:

769

C'est plus un art qu'une science. La documentation de Mongo sur les schémas est une bonne référence, mais voici quelques éléments à considérer:

  • Mettez autant que possible

    La joie d'une base de données de documents est qu'elle élimine beaucoup de jointures. Votre premier réflexe devrait être de mettre autant de documents que possible dans un seul document. Parce que les documents MongoDB ont une structure, et parce que vous pouvez interroger efficacement au sein de cette structure (cela signifie que vous pouvez prendre la partie du document dont vous avez besoin, donc la taille du document ne devrait pas vous inquiéter beaucoup), il n'y a pas de besoin immédiat de normaliser des données comme vous feriez en SQL. En particulier, toutes les données qui ne sont pas utiles en dehors de leur document parent doivent faire partie du même document.

  • Séparez les données qui peuvent être référencées à partir de plusieurs endroits dans sa propre collection.

    Il ne s'agit pas tant d'un problème «d'espace de stockage» que d'un problème de «cohérence des données». Si de nombreux enregistrements font référence aux mêmes données, il est plus efficace et moins sujet aux erreurs de mettre à jour un seul enregistrement et de conserver des références à d'autres endroits.

  • Considérations sur la taille du document

    MongoDB impose une limite de taille de 4 Mo (16 Mo avec 1,8) sur un seul document. Dans un monde de Go de données, cela semble petit, mais c'est aussi 30 000 tweets ou 250 réponses Stack Overflow typiques ou 20 photos scintillantes. D'un autre côté, il s'agit de bien plus d'informations que l'on ne voudrait présenter à la fois sur une page Web typique. Réfléchissez d'abord à ce qui facilitera vos requêtes. Dans de nombreux cas, les inquiétudes concernant la taille des documents seront une optimisation prématurée.

  • Structures de données complexes:

    MongoDB peut stocker des structures de données imbriquées profondes arbitraires, mais ne peut pas les rechercher efficacement. Si vos données forment un arbre, une forêt ou un graphique, vous devez effectivement stocker chaque nœud et ses bords dans un document distinct. (Notez qu'il existe des magasins de données spécialement conçus pour ce type de données que l'on devrait également prendre en compte)

    Il a également été souligné qu'il est impossible de renvoyer un sous-ensemble d'éléments dans un document. Si vous avez besoin de choisir quelques morceaux de chaque document, il sera plus facile de les séparer.

  • La cohérence des données

    MongoDB fait un compromis entre efficacité et cohérence. La règle est que les modifications apportées à un seul document sont toujours atomiques, tandis que les mises à jour de plusieurs documents ne doivent jamais être considérées comme atomiques. Il n'y a également aucun moyen de "verrouiller" un enregistrement sur le serveur (vous pouvez l'intégrer dans la logique du client en utilisant par exemple un champ "verrouiller"). Lorsque vous concevez votre schéma, réfléchissez à la manière dont vous garderez vos données cohérentes. Généralement, plus vous conservez dans un document, mieux c'est.

Pour ce que vous décrivez, j'intégrerais les commentaires et donnerais à chaque commentaire un champ id avec un ObjectID. L'ObjectID a un horodatage intégré afin que vous puissiez l'utiliser au lieu de créé à si vous le souhaitez.

John F. Miller
la source
1
Je voudrais ajouter à la question OP: Mon modèle de commentaires contient le nom d'utilisateur et le lien vers son avatar. Quelle serait la meilleure approche, étant donné qu'un utilisateur peut modifier son nom / avatar?
user1102018
5
En ce qui concerne les «structures de données complexes», il semble qu'il soit possible de renvoyer un sous-ensemble d'éléments dans un document en utilisant le cadre d'agrégation (essayez $ unwind).
Eyal Roth
4
Errr, cette technique n'était pas possible ou n'était pas largement connue dans MongoDB au début de 2012. Étant donné la popularité de cette question, je vous encourage à écrire votre propre réponse mise à jour. J'ai bien peur de m'être éloigné du développement actif sur MongoDB et je ne suis pas en bonne position pour vous adresser des commentaires dans mon post d'origine.
John F. Miller
54
16 Mo = 30 millions de tweets? alors environ 0,5 octet par tweet?!
Paolo
8
Oui, il semble que je sois parti d'un facteur 1000 et certaines personnes trouvent cela important. Je vais modifier le message. WRT 560 octets par tweet, lorsque j'ai noté cela en 2011, Twitter était toujours lié aux messages texte et aux chaînes Ruby 1.4; en d'autres termes toujours des caractères ASCII uniquement.
John F. Miller,
39

En général, l'intégration est bonne si vous avez des relations un-à-un ou un-à-plusieurs entre les entités, et la référence est bonne si vous avez des relations plusieurs-à-plusieurs.

ywang1724
la source
10
pouvez-vous s'il vous plaît ajouter un lien de référence? Merci.
db80
Comment trouvez-vous un commentaire spécifique avec cette conception d'un à plusieurs?
Mauricio Pastorini
29

Si je souhaite modifier un commentaire spécifié, comment obtenir son contenu et sa question?

Vous pouvez interroger par sous-document: db.question.find({'comments.content' : 'xxx'}).

Cela renverra l'intégralité du document Question. Pour modifier le commentaire spécifié, vous devez ensuite rechercher le commentaire sur le client, effectuer la modification et l'enregistrer dans la base de données.

En général, si votre document contient un tableau d'objets, vous constaterez que ces sous-objets devront être modifiés côté client.

Gates VP
la source
4
cela ne fonctionnera pas si deux commentaires ont un contenu identique. on pourrait dire que nous pourrions également ajouter un auteur à la requête de recherche, ce qui ne fonctionnerait toujours pas si l'auteur faisait deux commentaires identiques avec le même contenu
Steel Brain
@SteelBrain: s'il avait gardé l'index des commentaires, la notation par points pourrait aider. voir stackoverflow.com/a/33284416/1587329
serv-inc
13
Je ne comprends pas comment cette réponse a 34 votes positifs, la deuxième personne multiple commente la même chose que tout le système briserait. C'est une conception absolument terrible et ne doit jamais être utilisée. La façon dont @user le fait est la voie à suivre
user2073973
21

Eh bien, je suis un peu en retard mais je voudrais quand même partager ma façon de créer un schéma.

J'ai des schémas pour tout ce qui peut être décrit par un mot, comme vous le feriez dans la POO classique.

PAR EXEMPLE

  • Commentaire
  • Compte
  • Utilisateur
  • Blogpost
  • ...

Chaque schéma peut être enregistré en tant que document ou sous-document, je le déclare donc pour chaque schéma.

Document:

  • Peut être utilisé comme référence. (Par exemple, l'utilisateur a fait un commentaire -> le commentaire a une référence "faite par" à l'utilisateur)
  • Est une "racine" dans votre application. (Par exemple le blogpost -> il y a une page sur le blogpost)

Sous-document:

  • Ne peut être utilisé qu'une seule fois / n'est jamais une référence. (Par exemple, le commentaire est enregistré dans le blog)
  • N'est jamais une "racine" dans votre application. (Le commentaire n'apparaît que sur la page du blog, mais la page concerne toujours le blog)
Silom
la source
20

Je suis tombé sur cette petite présentation tout en recherchant cette question par moi-même. J'ai été surpris de la qualité de la présentation, à la fois de l'info et de sa présentation.

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

Il résume:

En règle générale, si vous avez beaucoup de [documents enfants] ou s'ils sont volumineux, une collection séparée peut être préférable.

Les documents plus petits et / ou moins ont tendance à être naturellement adaptés à l'intégration.

Chris Bloom
la source
11
Combien ça coûte a lot? 3? dix? 100? Quoi large? 1 Ko? 1 Mo? 3 champs? 20 champs? Qu'est-ce que smaller/ fewer?
Traxo
1
C'est une bonne question, et une pour laquelle je n'ai pas de réponse précise. La même présentation comprenait une diapositive qui disait "Un document, y compris tous ses documents et tableaux intégrés, ne peut pas dépasser 16 Mo", ce qui pourrait être votre coupure, ou tout simplement aller avec ce qui semble raisonnable / confortable pour votre situation spécifique. Dans mon projet actuel, la majorité des documents incorporés sont pour des relations 1: 1, ou 1: beaucoup où les documents incorporés sont vraiment simples.
Chris Bloom
Voir également le premier commentaire actuel de @ john-f-miller, qui, tout en ne fournissant pas de chiffres spécifiques pour un seuil, contient des pointeurs supplémentaires qui devraient aider à guider votre décision.
Chris Bloom
16

Je sais que c'est assez ancien mais si vous cherchez la réponse à la question de l'OP sur la façon de renvoyer uniquement le commentaire spécifié, vous pouvez utiliser l' opérateur $ (requête) comme ceci:

db.question.update({'comments.content': 'xxx'}, {'comments.$': true})
finspin
la source
4
cela ne fonctionnera pas si deux commentaires ont un contenu identique. on pourrait dire que nous pourrions également ajouter un auteur à la requête de recherche, ce qui ne fonctionnerait toujours pas si l'auteur faisait deux commentaires identiques avec le même contenu
Steel Brain
1
@SteelBrain: Bien joué monsieur, bien joué.
JakeStrang
12

Oui, nous pouvons utiliser la référence dans le document.Pour remplir un autre document comme sql i joins.In mongo db, ils n'ont pas de jointures pour mapper un à plusieurs documents de relation.Au lieu de cela, nous pouvons utiliser populate pour remplir notre scénario.

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

Le remplissage est le processus de remplacement automatique des chemins d'accès spécifiés dans le document par des documents provenant d'autres collections. Nous pouvons remplir un seul document, plusieurs documents, un objet simple, plusieurs objets simples ou tous les objets renvoyés par une requête. Regardons quelques exemples.

Mieux vous pouvez obtenir plus d'informations s'il vous plaît visitez: http://mongoosejs.com/docs/populate.html

Narendran
la source
5
Mongoose émettra une demande distincte pour chaque champ rempli. Ceci est différent de SQL JOINS car ils sont effectués sur le serveur. Cela inclut le trafic supplémentaire entre le serveur d'applications et le serveur mongodb. Encore une fois, vous pourriez considérer cela lorsque vous optimisez. Néanmoins, votre réponse est toujours correcte.
Max
6

En fait, je suis assez curieux de savoir pourquoi personne n'a parlé des spécifications UML. En règle générale, si vous avez une agrégation, vous devez utiliser des références. Mais s'il s'agit d'une composition, le couplage est plus fort et vous devez utiliser des documents intégrés.

Et vous comprendrez rapidement pourquoi c'est logique. Si un objet peut exister indépendamment du parent, vous voudrez y accéder même si le parent n'existe pas. Comme vous ne pouvez simplement pas l'intégrer dans un parent inexistant, vous devez le faire vivre dans sa propre structure de données. Et s'il existe un parent, il suffit de les lier ensemble en ajoutant une référence de l'objet dans le parent.

Vous ne savez pas vraiment quelle est la différence entre les deux relations? Voici un lien les expliquant: Agrégation vs Composition en UML

Bonjour123
la source
Pourquoi -1? Veuillez donner une explication qui clarifierait la raison
Bonjour123
1

Si je souhaite modifier un commentaire spécifié, comment puis-je obtenir son contenu et sa question?

Si vous aviez gardé une trace du nombre de commentaires et de l'index du commentaire que vous vouliez modifier, vous pourriez utiliser l'opérateur point ( exemple SO ).

Vous pourriez faire f.ex.

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

(comme une autre façon de modifier les commentaires à l'intérieur de la question)

serv-inc
la source