MongoDB 倾向于将数据都放在一个 Collection 下吗?
ringa_lee
ringa_lee 2017-04-24 09:09:33
0
3
777

举个例子,有一个用户信息和用户间关系的数据库,如果按照 SQL 的思路,会建立用户信息和用户关系两张表。那么,在 MongoDB 中,是倾向于将用户关系嵌入到用户信号,组成一个单独的文档吗?

ringa_lee
ringa_lee

ringa_lee

répondre à tous(3)
Peter_Zhu

Ce n’est pas le cas.

Il existe une limite supérieure à la taille d'un seul document dans une collection, qui est actuellement de 16 Mo, ce qui vous empêche de tout regrouper dans une collection. De plus, si la structure de la collection est trop complexe, cela affectera non seulement l'efficacité des requêtes et des mises à jour, mais entraînera également des difficultés de maintenance et des risques opérationnels. Avez-vous déjà essayé de sauvegarder accidentellement un document comme nul en vous serrant la main ? Quoi qu'il en soit, je l'ai fait si toutes les informations d'une personne se trouvent dans cette collection, cela doit être un sentiment assez amer.

Le principe général est :

  • Cluster selon la méthode de requête

    • Rassemblez les données qui doivent être lues fréquemment ensemble.
    • Rassemblez les informations qui sont logiquement étroitement liées.
    • Les données avec des exigences de réduction/agrégation de cartes sont rassemblées, et ces opérations ne peuvent fonctionner que sur une seule collection.
  • Répartir en fonction de la quantité de données

    • Si vous constatez que vous devez utiliser un tableau dans une collection et que la longueur du tableau continuera d'augmenter, vous devez alors placer le contenu des données dans une collection spéciale, et chaque élément de données fait référence à la clé primaire de le document actuel (tout comme le 1..N de mysql Identique aux dépendances de clé étrangère).
    • Si vous trouvez qu'une doc est trop profonde (plus de 2 niveaux), vous envisagerez probablement de la diviser, sinon il y aura des problèmes de performances et de maintenabilité.
  • Conception selon la structure de la table

    • MongoDB n'a pas le concept de structure de table, mais dans la réalité, il est rare de dire qu'une collection contient des documents avec des structures différentes. Si vous constatez que les différences dans les structures de documents deviennent de plus en plus grandes, vous devriez y réfléchir. comment les résumer dans une structure similaire, jeter les éléments modifiés dans d'autres collections et se référencer les uns les autres en utilisant des dépendances de clé étrangère.

Par exemple, lors de la conception d'un système utilisateur, la collection d'utilisateurs doit contenir des informations couramment utilisées telles que le nom, et doit également contenir des éléments comme lastLoginAt qui ne concernent que l'utilisateur. Peut-être que des informations sur les droits d'accès de l'utilisateur devraient également le faire. être inclus, mais n'incluez pas les informations du journal de connexion de l'utilisateur qui continueront d'augmenter.

En ce qui concerne la relation entre les utilisateurs, la question de savoir s'il existe une collection d'utilisateurs doit être discutée. Si vous avez seulement besoin de stocker la relation entre les utilisateurs et d'enregistrer les identifiants des amis, et que le nombre d'amis n'est pas trop grand, quelques centaines au maximum, alors j'ai tendance à les mettre dans une collection. Si les données relationnelles elles-mêmes sont plus complexes ou si le nombre d’amis se compte par milliers, j’ai tendance à les diviser.

De plus, le paradigme officiel de conception de modèles de données de Mongodb mérite d'être lu et je vous recommande d'y jeter un œil attentif.

刘奇

Adresse originale : http://pwhack.me/post/2014-06-25-1 Veuillez indiquer la source de réimpression

Cet article est extrait du chapitre 8 de « The Definitive Guide to MongoDB », qui peut répondre en profondeur aux deux questions suivantes :

  • /q/1010000000364944
  • /q/1010000000364944

Il existe de nombreuses façons de représenter les données, et l'une des questions les plus importantes est de savoir dans quelle mesure les données doivent être normalisées. La normalisation est le processus de dispersion des données dans plusieurs collections différentes, et différentes collections peuvent référencer les données les unes des autres. Bien que de nombreux documents puissent référencer une certaine donnée, cette donnée n’est stockée que dans une seule collection. Par conséquent, si vous souhaitez modifier cette donnée, il vous suffit de modifier le document qui enregistre cette donnée. Cependant, MongoDB ne fournit pas d'outil de jointure, plusieurs requêtes sont donc nécessaires pour effectuer des requêtes de jointure entre différentes collections.

La dénormalisation est l'opposé de la normalisation : intégrer les données requises pour chaque document dans le document. Chaque document possède sa propre copie des données, plutôt que tous les documents référençant collectivement la même copie des données. Cela signifie que si les informations changent, tous les documents associés doivent être mis à jour, mais lorsqu'une requête est exécutée, une seule requête est nécessaire pour obtenir toutes les données.

Décider quand normaliser et quand dénormaliser est difficile. La normalisation peut améliorer la vitesse d'écriture des données et la dénormalisation peut améliorer la vitesse de lecture des données. Cela doit être soigneusement mis en balance avec les dizaines de besoins de votre propre application.

Exemples de représentation des données

Supposons que vous souhaitiez enregistrer les informations sur les étudiants et les cours. Une façon de représenter cela consiste à utiliser une collection d'étudiants (chaque étudiant est un document) et une collection de classes (chaque cours est un document). Utilisez ensuite la troisième collection studentClasses pour sauvegarder la relation entre les étudiants et les cours.

> db.studentsClasses.findOne({"studentsId": id});
{
  "_id": ObjectId("..."),
  "studentId": ObjectId("...");
  "classes": [
    ObjectId("..."),
    ObjectId("..."),
    ObjectId("..."),
    ObjectId("...")
  ]
}

Si vous êtes familier avec les bases de données relationnelles, vous avez peut-être déjà créé ce type de jointure de table, même si vous ne pouvez avoir qu'un seul étudiant et un seul cours dans chaque document d'inaptitude (plutôt qu'une liste de "_id" de cours). Placer les cours dans un tableau est un peu le style de MongoDB, mais en pratique, vous n'enregistrez généralement pas de données comme celle-ci car il faut de nombreuses requêtes pour obtenir les informations réelles.

Supposons que vous souhaitiez trouver un cours choisi par un étudiant. Vous devez d'abord rechercher dans la collection des étudiants pour trouver les informations sur les étudiants, puis interroger les studentClasses pour trouver le cours "_id", et enfin interroger la collection de classes pour obtenir les informations souhaitées. Afin de connaître les informations sur le cours, trois requêtes doivent être demandées au serveur. Il est probable que vous ne souhaitiez pas utiliser ce type d'organisation des données dans MongoDB, à moins que les informations sur les étudiants et les cours ne changent fréquemment et qu'il n'y ait aucune exigence en matière de vitesse de lecture des données.

Vous pouvez enregistrer une requête si vous intégrez la référence du cours dans le document étudiant :

{
  "_id": ObjectId("..."),
  "name": "John Doe",
  "classes": [
    ObjectId("..."),
    ObjectId("..."),
    ObjectId("..."),
    ObjectId("...")
  ]
}

Le champ "classes" est un tableau qui stocke le "_id" des cours que John Doe doit suivre. Lorsque vous avez besoin de trouver des informations sur ces cours, vous pouvez utiliser ces "_id" pour interroger la collection de classes. Ce processus ne nécessite que deux requêtes. Cette façon d'organiser les données est idéale si les données ne doivent pas être consultées à tout moment et ne changent à aucun moment (« à tout moment » est plus exigeant que « fréquemment »).

Si vous avez besoin d'optimiser davantage la vitesse de lecture, vous pouvez dénormaliser complètement les données et enregistrer les informations de cours en tant que document intégré dans le champ « cours » du document étudiant. De cette manière, vous pouvez obtenir les informations de cours de l'étudiant. avec une seule requête :

{
  "_id": ObjectId("..."),
  "name": "John Doe"
  "classes": [
    {
      "class": "Trigonometry",
      "credites": 3,
      "room": "204"
    },
    {
      "class": "Physics",
      "credites": 3,
      "room": "159"
    },
    {
      "class": "Women in Literature",
      "credites": 3,
      "room": "14b"
    },
    {
      "class": "AP European History",
      "credites": 4,
      "room": "321"
    }
  ]
}

L'avantage de la méthode ci-dessus est qu'elle ne nécessite qu'une seule requête pour obtenir les informations de cours de l'étudiant. L'inconvénient est qu'elle prendra plus d'espace de stockage et que la synchronisation des données est plus difficile. Par exemple, si la physique devient un crédit de 4 points (au lieu d'une note de 3 points), alors chaque étudiant ayant suivi le cours de physique devra mettre à jour sa documentation, et pas seulement le document « Physique ».

Enfin, vous pouvez également mélanger des données intégrées et des données de référence : créez un tableau de sous-documents pour enregistrer des informations communes et recherchez le document réel par référence lorsque vous avez besoin d'interroger des informations plus détaillées :

{
  "_id": ObjectId("..."),
  "name": "John Doe",
  "classes": [
    {
      "_id": ObjectId("..."),
      "class": "Trigonometry"    
    },
    {
      "_id": ObjectId("..."),
      "class": "Physics"
    }, {
      "_id": ObjectId("..."),
      "class": "Women in Literature"
    }, {
      "_id": ObjectId("..."),
      "class": "AP European History"
    }
  ]
}

Cette méthode est également un bon choix, car les informations intégrées peuvent être modifiées en fonction des besoins. Si vous souhaitez inclure plus (ou moins) d'informations sur une page, vous pouvez ajouter plus (ou plus) (moins) d'informations. placé dans le document en ligne.

Une autre question importante à considérer est la suivante : les informations sont-elles mises à jour plus fréquemment ou les informations sont-elles lues plus fréquemment ? Si les données sont mises à jour régulièrement, la normalisation est un meilleur choix. Si les données changent rarement, cela ne vaut pas la peine de sacrifier la vitesse de lecture et d'écriture pour optimiser l'efficacité des mises à jour.

Par exemple, un exemple d'introduction à la normalisation dans un manuel pourrait consister à enregistrer les utilisateurs et les adresses des utilisateurs dans des collections distinctes. Cependant, les gens changent rarement d'adresse, donc l'efficacité de chaque requête ne doit pas être sacrifiée au cas extrêmement improbable où quelqu'un change d'adresse. Dans ce cas, l'adresse doit être intégrée dans le document utilisateur.

Si vous décidez d'utiliser des documents en ligne, vous devez configurer une tâche cron lors de la mise à jour des documents pour vous assurer que tous les documents sont mis à jour avec succès à chaque mise à jour. Par exemple, nous avons essayé de diffuser une mise à jour sur plusieurs documents et le serveur est tombé en panne avant que la mise à jour n'ait terminé tous les documents. Il faut pouvoir détecter ce problème et refaire la mise à jour inachevée.

De manière générale, plus les données sont générées fréquemment, moins il est probable qu'elles soient intégrées dans d'autres documents. Si le nombre de champs incorporés ou de champs incorporés augmente indéfiniment, ces contenus doivent être enregistrés dans une collection distincte et accessibles à l'aide de références au lieu d'être intégrés dans d'autres documents. Les informations telles que les listes de commentaires ou les listes d'activités doivent être enregistrées dans une collection distincte et doivent être enregistrées. ne pas être intégré dans d’autres documents.

Enfin, si certains champs font partie des données du document, alors ces champs doivent être intégrés dans le document. Si vous devez souvent exclure un champ lors de l'interrogation de documents, ce champ doit être placé dans une autre collection plutôt que intégré dans le document actuel.

更适合内嵌 更适合引用
子文档较小 子文档较大
数据不会定期改变 数据经常改变
最终数据一致即可 中间阶段的数据必须一致
文档数据小幅增加 文档数据大幅增加
数据通常需要执行二次查询才能获得 数据通常不包含在结果中
快速读取 快速写入

Supposons que nous ayons une collection d'utilisateurs. Vous trouverez ci-dessous certains champs qui peuvent être obligatoires et s'ils doivent être intégrés dans le document utilisateur.

Préférences utilisateur (préférences du compte)

Les préférences utilisateur ne concernent qu'un utilisateur spécifique et devront probablement être interrogées avec d'autres informations utilisateur dans le document utilisateur. Les préférences utilisateur doivent donc être intégrées dans le document utilisateur.

Activité récente

Ce champ dépend de la fréquence à laquelle l'activité a augmenté et changé récemment. S'il s'agit d'un champ de longueur fixe (comme les 10 derniers événements), alors ce champ doit être intégré dans le document utilisateur.

Amis

Habituellement, vous ne devez pas intégrer les informations sur vos amis dans les documents utilisateur, du moins pas entièrement. La section suivante présentera le contenu pertinent des applications de réseaux sociaux.

Tous les contenus générés par les utilisateurs

Ne doit pas être intégré dans la documentation utilisateur.

Base

Le nombre de références à d'autres collections contenues dans une collection est appelé cardinalité. Les relations courantes incluent un à un, un à plusieurs et plusieurs à plusieurs. Supposons qu'il existe une application de blog. Chaque article de blog a un titre, qui est une relation individuelle. Chaque auteur peut avoir plusieurs articles, ce qui constitue une relation un-à-plusieurs. Chaque article peut avoir plusieurs balises (tags), et chaque balise peut être utilisée dans plusieurs articles, il s'agit donc d'une relation plusieurs-à-plusieurs.

Dans MongoDB, beaucoup peuvent être divisés en deux sous-catégories : plusieurs et quelques-uns. Par exemple, la relation entre les auteurs et les articles peut être une relation biunivoque : chaque auteur ne publie que quelques articles. Il peut y avoir une relation plusieurs à quelques-uns entre les articles de blog et les balises : le nombre d'articles peut en fait être supérieur au nombre de balises. Il existe une relation un-à-plusieurs entre les articles de blog et les commentaires : chaque article peut contenir plusieurs commentaires.

Tant que la relation entre moins et plus est déterminée, il est plus facile de faire un compromis entre les données intégrées et les données référencées. D'une manière générale, il est préférable d'utiliser la méthode en ligne pour les relations « moins », et il est préférable d'utiliser la méthode de référence pour les relations « plusieurs ».

Amis, fans et autres choses gênantes

Gardez vos amis proches et restez loin des ennemis

De nombreuses applications sociales doivent relier des personnes, du contenu, des fans, des amis et d'autres choses. Le compromis entre l’utilisation de formulaires en ligne et référencés pour ces données très pertinentes n’est pas facile. Cette section présentera les considérations liées aux données des graphiques sociaux. Souvent, les suivis, les amis ou les favoris peuvent être simplifiés dans un système de publication-abonnement : un utilisateur peut s'abonner aux notifications liées à un autre utilisateur. De cette manière, deux opérations de base doivent être efficaces : comment enregistrer les abonnés et comment informer tous les abonnés d'un événement.

Il existe trois méthodes courantes de mise en œuvre d'un abonnement. La première façon consiste à intégrer le producteur de contenu dans le document de l'abonné :

{
    "_id": ObjectId("..."),
    "username": "batman",
    "email": "batman@waynetech.com",
    "following": [
        ObjectId("..."),
        ObjectId("...")
    ]
}

Désormais, pour un document utilisateur donné, vous pouvez utiliser le formulaire db.activities.find({"user": {"$in": user["following"]}}) pour interroger toutes les informations d'activité qui intéressent l'utilisateur. Cependant, pour une information d'activité qui vient d'être diffusée, si vous souhaitez connaître tous les utilisateurs intéressés par cette information, vous devez interroger le champ "suivant" de tous les utilisateurs.

Une autre façon consiste à intégrer l'abonné dans le document du producteur :

{
    "_id": ObjectId("..."),
    "username": "joker",
    "email": "joker@mailinator.com",
    "followers": [
        ObjectId("..."),
        ObjectId("..."),
        ObjectId("...")
    ]
}

Lorsque ce producteur publie un nouveau message, nous pouvons immédiatement savoir quels utilisateurs doivent être avertis. L’inconvénient est que si vous avez besoin de trouver une liste d’utilisateurs suivis par un utilisateur, vous devez interroger l’ensemble de la collection d’utilisateurs. Les avantages et les inconvénients de cette méthode sont exactement opposés à ceux de la première méthode.

En même temps, les deux méthodes ont un autre problème : elles rendront les documents des utilisateurs de plus en plus volumineux et changeront de plus en plus fréquemment. Souvent, les champs « follow » et « followers » n'ont même pas besoin d'être renseignés : à quelle fréquence la liste des followers est-elle interrogée ? Si les utilisateurs suivent certaines personnes plus fréquemment ou ne suivent plus certaines personnes, cela entraînera également une grande fragmentation. Par conséquent, la solution finale normalise davantage les données et enregistre les informations d'abonnement dans une collection distincte pour éviter ces défauts. Ce type de normalisation peut être un peu excessif, mais il est utile pour les champs qui changent fréquemment et qui n'ont pas besoin d'être renvoyés avec le reste du document. Il est logique de procéder à cette normalisation du champ « abonnés ».

Utiliser une collection pour sauvegarder la relation entre éditeurs et abonnés. La structure du document peut être la suivante :

{
    "_id": ObjectId("..."),   //被关注者的"_id"
    "followers": [
        ObjectId("..."),
        ObjectId("..."),
        ObjectId("...")
    ]
}

Cela peut simplifier le document utilisateur, mais nécessite des requêtes supplémentaires pour obtenir la liste des fans. Étant donné que la taille du tableau « followers » change souvent, « usePowerOf2Sizes » peut être activé sur cette collection pour garantir que la collection des utilisateurs est aussi petite que possible. Si la collection d'abonnés est stockée dans une autre base de données, elle peut être compressée sans trop affecter la collection des utilisateurs.

Faire face à l'effet Wil Wheaton

Quelle que soit la stratégie utilisée, les champs en ligne ne peuvent fonctionner efficacement que lorsque le nombre de sous-documents ou de références n'est pas particulièrement important. Pour les utilisateurs plus connus, cela peut provoquer un débordement du document utilisé pour enregistrer la liste des fans. Une solution à cette situation consiste à utiliser des documents « continus » lorsque cela est nécessaire. Par exemple :

> db.users.find({"username": "wil"})
{
    "_id": ObjectId("..."),
    "username": "wil",
    "email": "wil@example.com",
    "tbc": [
        ObjectId("123"),    // just for example
        ObjectId("456")     // same as above
    ],
    "followers": [
        ObjectId("..."),
        ObjectId("..."),
        ObjectId("..."),
        ...
    ]
}
{
    "_id": ObjectId("123"),
    "followers": [
        ObjectId("..."),
        ObjectId("..."),
        ObjectId("..."),
        ...
    ]
}
{
    "_id": ObjectId("456"),
    "followers": [
        ObjectId("..."),
        ObjectId("..."),
        ObjectId("..."),
        ...
    ]
}

Pour cette situation, vous devez ajouter une logique pertinente pour récupérer les données du tableau "à confirmer" (à suivre) dans l'application.

Dis quelque chose

Pas de solution miracle.

伊谢尔伦

Si l'entreprise a toujours besoin d'interroger la relation entre les utilisateurs, il est préférable de séparer la relation dans une collection distincte

Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal