【问题标题】:Structuring mogodb schema构建 mongodb 模式
【发布时间】:2021-08-11 04:28:57
【问题描述】:

我在玩 MongoDB,想知道 SQL ish 模式如何与 MongoDB 相对应的最佳实践是什么。以下是我目前拥有的表格/数据:

  • user
    • 身份证
    • 电子邮件
    • 姓名
  • answer
    • user_id (FK user.id)
    • 标签
    • 点赞
  • repo
    • 身份证
    • 所有者
    • 姓名
    • 说明
    • 星星
  • repo_tag
    • repo_id(FK 到 repo.id)
    • 标签
    • is_language
    • 百分比
  • repo_contrib
    • repo_id(FK 到 repo.id)
    • user_id(FK 到 user.id)
    • lines_of_code

结构如下:

  • 用户
    • 回答(左外)
    • repo_contrib(左外)
      • 回购
        • repo_tag

注意:所有用户都至少有一个答案或一个 repo,但不一定必须同时拥有。

如何将其放入 mongo 架构中?这会是一个“收藏”吗?或者这将是两个集合:一个用于用户,一个用于 repo;还是更多?

我的查询将类似于:“使用 tay [Python] 获得超过 2 个赞成票的答案或带有超过 2 个星的 [Python] 标签的 repo 的所有用户。

【问题讨论】:

  • 要使用 MongoDB 的灵活模式进行建模,所需的细节之一是数据的数量/大小。影响模型的重要因素(除其他外)是大小和使用情况(重要的查询,包括所有 CRUD 操作)。

标签: mongodb schema database-schema


【解决方案1】:

让我把这分成几个步骤:

第 1 步 - MONGODB 和 MONGOOSE

MongoDB 是一个基于文档的数据库。集合中的每条记录都是一个文档,并且每个文档都应该是自包含的(它应该包含您在其中需要的所有信息)。

由于 MongoDB 是一个无关系数据库,您不能在集合之间创建关系,但您可以将一个集合文档的引用存储为另一个集合文档的属性。为了帮助您管理所有这些,有一个名为Mongoose 的很棒的包,它允许您为每个集合创建一个模型。定义后 模型,Mongoose 将允许您轻松地对数据库进行查询。

第 2 步 - 定义模型

正如我们所说,文档应该是独立的,因此它们应该包含您需要的所有信息。根据您的示例,我们可以有两种方法:

方法 1:

为关系数据库中的每个表创建一个集合。当您拥有包含大量数据的文档时,这是最佳做法,因为它是可扩展的。

方法 2:

创建 3 个集合 - USERS、ANSWERS 和 REPOS。因为repo_contrib 没有很多数据,所以可以将所有用户的贡献存储在一个USERS 文档中。这样,当您获取用户文档时,您将在一个地方拥有所需的一切。 repo_tag 也是如此——我们可以将所有 repo 的标签存储在一个 REPOS 文档中。

方法 3:

创建 2 个集合 - USERS 和 REPOS。同 APPROACH 2,但您也可以将所有用户的answers 添加到 USERS 文档中。

建议:

在这种情况下,我会使用 APPROACH 2,因为 repo_contribrepo_tag 不存储大数据,并且可以轻松存储在 USERS 和 REPOS 文档中,没有问题。此外,如果我们采用这种方法,它将使查询数据库变得更加容易。我没有选择选项 3 的原因是因为理论上用户可以有数千或数万个答案,并且无法很好地扩展。

第 3 步 - 实施

注意:MongoDB 会自动为每个文档分配_id,因此您在实现模型时不必定义id 属性。

您的关系数据库示例中的表可以像这样映射到集合(此实现适用于 APPROACH 2):

用户收藏:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

var schema = new Schema({
    email: { type: String, required: true, unique: true },
    name: { type: String, required: true, unique: false },
    contributions: [{
      repo_id: { type: mongoose.Schema.Types.ObjectId, ref: 'REPOS' },
      lines_of_code: { type: Numeric, ref: 'REPOS' }
    }]
});
const Users = mongoose.model('USERS', schema);
module.exports = Users;

答案集合:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

var schema = new Schema({
    user_id: { type: mongoose.Schema.Types.ObjectId, ref: 'USERS', required: true },
    tag: { type: String, required: true, unique: false },
    upvotes:{ type: Number, default: 0, unique: false }
});
const Answers = mongoose.model('ANSWERS', schema);
module.exports = Answers;

REPOS 集合:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

var schema = new Schema({
    owner: { type: mongoose.Schema.Types.ObjectId, ref: 'USERS', required: true },
    name: { type: String, required: true, unique: false },
    description: { type: String, required: false, unique: false },
    stars:{ type: Number, default: 0, unique: false },
    tags: [{
      name:  { type: String, required: true, unique: false },
      is_language: {type: Boolean, required: true, unique: false},
      percentage:{ type: Number, default: 0, unique: false }
    }]
});
const Repos = mongoose.model('REPOS', schema);
module.exports = Repos ;

第 4 步 - 人口和数据库查询

Mongoose 的最佳功能之一称为population。如果您将一个集合文档的引用存储为另一个集合文档的属性,则在执行数据库查询时,Mongoose 会将引用替换为实际文档。

示例 1:

让我们首先以您建议的第一个查询为例:Find all users with an Answer with tag [Python] with more than 2 upvotes。由于我们将user_id 存储在 ANSWERS 集合中作为对来自 USERS 集合的文档的引用,这意味着我们可以只查询 ANSWERS 集合,并且当返回最终结果时,Mongoose 将转到 USERS 集合并将引用替换为实际的用户文档。将执行此操作的数据库查询如下所示:

const ANSWERS = require('../models/answers');

ANSWERS.find({
  "tag": "Python",
  "upvotes": {
    "$gt": 2
  }
}).populate('user_id');

示例 2:

您建议的第二个查询是:Find all repos with the [Python] tag with more than two stars。由于我们将所有 repo 的标签存储在一个数组中,我们只需要检查该数组是否包含具有等于 Pythonname 字段的项目,并且 repo 的 stars 字段大于 2。将执行此操作的数据库查询如下所示:

const REPOS = require('../models/repos');

REPOS.find({
  "tags.name": "Python",
  "stars": {
    "$gt": 2
  }
})

这也是工作示例:https://mongoplayground.net/p/rgBtVVDgPzG

【讨论】:

    【解决方案2】:

    设计数据库模型在大多数时候都非常复杂,我猜您正在寻找最佳实践来自信誉良好的来源。我认为这是其他答案中的缺失点,即使@NenadMilosavljevic 接近它。

    NoSQL建模简介

    您可能习惯于对 SQL 数据库建模,对于 NoSQL 建模则完全不同。这些是一些差异:

    SQL Modeling NoSQL modeling
    This type of modeling is "data-oriented" in the sense that it is designed to be used and shared by many applications. Data is normalized, generally using normal forms, to avoid duplication and to make future changes easier and with lowest downtime possible. NoSQL modeling is "application-oriented" because it should be built from the requirements of a single application, in order to reach the maximum level of optimization.
    You start from requirements analysis, then the conceptual design, in the end the physical design. If you want to optimize your application, you need to start from the app itself and from the operations needed: this is the so-called workload. After that there are conceptual and physical design of course.

    我想更多地关注工作量,因为它非常重要。由于您来自基于 SQL 的应用程序,您可以从各种场景、生产日志和统计信息开始描述工作负载。对于您需要的每个查询,这些参数都是必不可少的:

    • 请求的数据大小
    • 查询频率
    • 所涉及操作的复杂性

    回到最初的问题:“我的查询将类似于...” 不足以让我帮助您构建 NoSQL 模型。您的问题有很多解决方案,但除非您提供更多有关您需要执行的查询的信息,否则它们都是正确的。

    @NenadMilosavljevic 为您提供了多种方法,但由于我在上面告诉您的原因,我不能说第二种方法是否正确。例如,他建议将user 和用户贡献放在一起,这样您就必须执行单个查询来检索它们,而不是执行JOINs 或更昂贵的操作。 这当然很聪明,但假设(可能不是您的情况)您必须经常更新用户贡献,那么在这种情况下,将它们保存在单独的集合中可能会更好。

    我的意思是缺少太多假设,我们为您提供的解决方案可能很好但不是最优的。 老实说,我不清楚您是否需要从 SQL 模型到 NoSQL 模型的简单转换,或者您正在尝试应用 NoSQL 原则。我不知道您的数据库的大小,但如果性能不是问题,请使用您认为更合适的解决方案。研究如何更好地为您的数据建模是浪费时间。

    相反,如果你真的需要设计一个 NoSQL 数据库,而不是类似 SQL 的 NoSQL 数据库,那么我的建议是遵循this course。实际上,您可以在不到 5 小时内完成它,而且在您的情况下不需要很多课程,但值得一看。例如,这里没有人谈论模式以及如何处理一对数的关系。除非您想在为时已晚时重新设计数据库,否则了解它们的存在非常重要。

    【讨论】:

      【解决方案3】:

      这是我的建议。我的建议是 3 个系列。有userrepoanswer。以下是供参考的架构。

      用户集合

      id: String
      email: String
      name: String
      

      回购集合

      id: String
      owner: String
      name: String
      description: String
      tag: [String] // Array of string
      contributors: [Number] // Array of user id
      

      我建议使用另一个名为 answer 的集合。这是因为用户可以提供很多答案。因此,与将其放在 user 集合的子文档中相比,将其放在另一个集合上会更容易查询。

      答案集合

      answer_id
      user_id
      tag
      upvotes
      

      希望对你有帮助。

      【讨论】:

      • 谢谢,将这三个放在不同的集合中有什么好处?另外,user 是主要对象,通过贡献者列表字段加入用户不是很困难吗?也就是说,获取给定用户的所有存储库?
      • 优势实际上取决于您将如何查询数据。现在,我只是根据一些逻辑思维来区分它。我的假设是 - 列出用户给出的所有答案 - 列出所有 repo - 列出属于用户的所有 repo 等等。使用 MongoDB 可以轻松获取给定用户的所有 repo。您可以使用 $in 运算符来查找用户 ID 是否存在于贡献者中。因此,您可以获得给定用户的所有 repo。
      • 我已经为这个问题添加了赏金。想要详细说明?那么连接查询会是什么样子?
      【解决方案4】:

      Mongo 架构设计 101:https://www.mongodb.com/blog/post/6-rules-of-thumb-for-mongodb-schema-design-part-1

      如果在 SQL 中您以面向对象的方式考虑数据 - 您的模型代表一些业务实体 并且您围绕它们构建功能,在 Mongo 中您应该以功能方式思考 - 您拥有哪些数据 输入和您需要的输出。换句话说,您的架构应该基于您需要的查询 运行,而不是基于您拥有的数据。

      这有点棘手,因为没有最好的方法 - 任何模式对于某些查询都会比其他查询更好。 您将需要选择应优先考虑哪些查询。为了让它更有趣,你需要 预测您将来可能遇到的问题。

      当然,这一切都与数据大小有关。如果它适合单个服务器,则您可以使用聚合查找来“加入”集合。 否则分片会严重限制您的选择。

      另一方面,嵌入应该小心使用 - 文档大小不能超过 16MB 和修改 嵌入文档并不是那么简单。

      要考虑的最后但并非最不重要的事情是索引。您的架构应该为您的查询提供有效的索引。在这里,您不仅需要考虑数据大小,还需要考虑其质量 - 选择性/基数

      鉴于上述情况,“使用 tay [Python] 获得超过 2 个赞成票的答案或带有 [Python] 标记且超过两颗星的 repo 的所有用户”的最佳模式将是 2 个集合:

      用户:

      {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "type": "object",
        "properties": {
          "id": {
            "bsonType": "objectId"
          },
          "email": {
            "bsonType": "string"
          },
          "name": {
            "bsonType": "string"
          },
          "answers": {
            "bsonType": "array",
            "items": [
              {
                "bsonType": "object",
                "properties": {
                  "tag": {
                    "bsonType": "string"
                  },
                  "upvotes": {
                    "bsonType": "int"
                  }
                },
                "required": [
                  "tag",
                  "upvotes"
                ]
              }
            ]
          },
        },
        "required": [
          "id",
          "email",
          "name",
          "answers"
        ]
      }
      

      回购:

      {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "type": "object",
        "properties": {
          "id": {
            "bsonType": "objectId"
          },
          "owner": {
            "bsonType": "string"
          },
          "name": {
            "bsonType": "string"
          },
          "description": {
            "bsonType": "string"
          },
          "stars": {
            "bsonType": "int"
          },
          "tags": {
            "bsonType": "array",
            "items": [
              {
                "bsonType": "object",
                "properties": {
                  "tag": {
                    "bsonType": "string"
                  },
                  "is_language": {
                    "bsonType": "bool"
                  },
                  "percentage": {
                    "bsonType": "double"
                  }
                },
                "required": [
                  "tag",
                  "is_language",
                  "percentage"
                ]
              }
            ]
          },
          "contributors": {
            "bsonType": "array",
            "items": [
              {
                "bsonType": "object",
                "properties": {
                  "user_id": {
                    "bsonType": "objectid"
                  },
                  "lines_of_code": {
                    "bsonType": "int"
                  }
                },
                "required": [
                  "user_id",
                  "lines_of_code"
                ]
              }
            ]
          }
        },
        "required": [
          "id",
          "owner",
          "name"
          "description",
          "stars"
        ]
      }
      

      查询:

      db.user.find({answers: {$elemMatch:{tag:"Python", upvotes:{$gt:2}}}})
      db.repo.find({"tags.tag":"Python", stars:{$gt:2}})
      

      在评论中,您提到了“获取给定用户的所有存储库”之类的内容。假设它是关于贡献者的,否则你根本不需要这个数组。 查询将是:

      db.repo.find({"contributors.user_id": ObjectId("12313212313232")})
      

      【讨论】:

        猜你喜欢
        • 2017-03-29
        • 1970-01-01
        • 1970-01-01
        • 2011-10-02
        • 2011-06-20
        • 1970-01-01
        • 2021-09-02
        • 2017-12-16
        • 2022-01-02
        相关资源
        最近更新 更多