什么是 MongoDB?特性、文档建模及使用时机

MongoDB 是一种将数据存储为文档的数据库,而不是主要将其组织到关系表中。可考虑用于通常一起读取的数据,例如具有不同属性的产品目录或嵌套用户资料,同时仍需设计模式并检查性能。

本指南解释了 MongoDB 的存储模型、功能、使用案例和采用标准。有关更广泛的背景,请参阅 NoSQL 模型与关系数据库的比较 。

MongoDB 如何存储数据?

文档由字段-值对组成。集合是文档的逻辑分组,数据库可以包含用于不同目的的集合,例如产品、订单和客户。

MongoDB 将文档存储为 BSON(二进制 JSON)。BSON 以二进制形式表示 JSON(JavaScript 对象表示法)结构,并支持其他类型,包括日期和 ObjectId 值。因此,MongoDB 不应该被理解为仅仅保存 JSON 文本文件的存储。文档参考 解释了类型和标识符规则。

这个虚构的产品 JSON 展示了文档的结构。在标准集合中,_id 标识一个文档。示例使用了可读字符串;应用程序仍然需要一个标识符格式和重复处理规则。

{
  "_id": "book-101",
  "name": "Database Basics",
  "category": "books",
  "price_krw": 24000,
  "attributes": {
    "author": "Mina Kim",
    "pages": 320
  },
  "tags": [
    "database",
    "beginner"
  ]
}

attributes 是一个嵌套对象,tags 是一个数组。书籍可以有作者和页数字段,而电子产品有电池信息。保留价格类型、必填字段和缺失值的共享规则。

主要特征及其条件

灵活的模式和验证

模式定义字段、类型和必填条件。默认情况下,MongoDB 不要求集合中的每个文档具有相同的字段。这种灵活性并不消除模式设计。

模式验证 可以限制允许的类型和值范围。协调应用程序检查和数据库验证,以防止混合数字/字符串价格或不一致的日期表示。在向现有数据添加规则之前,识别不符合的文档并决定如何处理更新。

查询、聚合和相关数据

MongoDB 查询使用字段条件和运算符,而不是直接接受 SQL 语句。学习您的语言驱动程序或 shell 使用的语法。点符号访问嵌套字段,查询还可以表达数组条件。

聚合管道通过过滤、分组和计算等阶段处理文档。例如,筛选未取消的订单项并按产品统计总数量。聚合管道指南 解释了各个阶段的工作方式。

$lookup 提供了与另一个集合的左外连接,因此“MongoDB 没有连接”是不准确的。请查看 $lookup 支持和性能条件。不要假设引用会自动获得与关系型外键约束相关的参照完整性执行。

索引与性能

索引是一种数据结构,可以减少查找匹配文档时需要检查的数据量。单字段索引和复合索引适用于不同的访问模式。索引文档解释了查询的好处和写入成本。

更多的索引需要存储空间,并在写入时进行维护。性能还取决于文档大小、读/写比例、内存、磁盘和网络。使用执行计划和实际负载评估代表性查询,而不是假设NoSQL天生就快。

事务和读/写保证

单文档更新是原子的:它不会部分应用该更新。对于必须在多个文档上共同成功或失败的更改,请考虑在副本集或分片集群上使用多文档事务。不要假设独立服务器上具有相同的功能。

ACID 意味着原子性、一致性、隔离性和持久性。请查阅 事务文档 中的部署和配置条件。当订单创建和库存减少影响不同文档时,需要定义事务边界、并发请求处理和重试行为。

读取关注(read concern)控制读取可观察到的数据状态,写入关注(write concern)控制写入成功的确认级别。在告知用户付款完成之前,应先确定所需的保证,并区分哪些操作可以接受从副本读取的旧值。

相关数据应嵌入还是引用?

嵌入将相关数据放入一个文档中。引用链接到另一个文档的标识符。建模指南 建议将经常一起访问的数据存放在一起;嵌入与引用指南 解释了关系和更新的权衡。

决策区域 考虑在以下情况下嵌入 考虑在以下情况下引用
阅读 父项和相关详细信息通常一起阅读 相关详细信息独立阅读
更新 值属于一个更新单元 共享信息独立变化
大小 该组有有限的大小和数量 项目持续增长或很大
示例 订单的送货地址和购买的产品名称 完整的评价历史或共享的客户资料

将送货地址复制到订单中可以保留其历史含义。将客户的当前联系方式复制到每个订单中,会产生需要随联系方式变化而更新的副本。首先区分历史事实与必须始终保持最新的值。

普通 BSON 文档的最大大小为 16 MiB(二进制兆字节)。将每条评论或日志条目附加到一个数组中可能会遇到该限制,并增加更新成本。在使用时,请考虑使用单独的集合、引用或受限组,并注意 文档大小限制。

哪些使用场景值得考虑?

这些是评估文档模型的理由,而不是基于行业标签的保证。实际读取和更新应该驱动选择。

使用案例 考虑文档的理由 需要检查的条件
产品目录 不同的属性和嵌套的产品详细信息 共享字段的验证和过滤/排序索引
个人资料和内容 在一个屏幕上读取相关属性 访问权限、大型数组和重复数据
事件和设备记录 特定于事件的结构和额外属性 保留、设备/时间查询和容量增长
订单 订单状态的快照 库存/付款正确性、并发更新和事务

当灵活的跨表报表、共享关系和约束,或复杂的多对多更新是核心需求时,也要比较关系型数据库。仅存储 JSON 并不足以替换现有数据库。检查文档模型是否改善了重要查询和运维工作负载。

一个小的插入和查询示例

以下命令使用 MongoDB Shell,mongosh。首先连接到一个单独的练习部署,使用一个对目标数据库具有读取、写入和创建索引权限的账户,并安装了 Shell。有关连接说明,请参见 MongoDB Shell 文档。

示例在mongodb_intro_demo数据库的product_catalog集合中插入一个商品。首次运行时,请使用该集合为空的练习环境,并在执行命令前确认连接目标确实是该练习环境。

db = db.getSiblingDB("mongodb_intro_demo");

db.product_catalog.insertMany([
{
  "_id": "book-101",
  "name": "Database Basics",
  "category": "books",
  "price_krw": 24000,
  "attributes": {
    "author": "Mina Kim",
    "pages": 320
  },
  "tags": [
    "database",
    "beginner"
  ]
}
]);

insertMany() 插入文档。在标准集合中重复相同的 _id 会产生重复键错误。为了反复练习,请使用一个新的练习数据库,或明确地识别并仅处理你添加的示例文档。不要删除现有集合来解决该错误。

此查询查找价格低于 30,000 韩元的 books 产品,返回它们的标识符、名称和价格,并按价格升序排序。在仅存在示例数据的情况下,book-101 匹配条件。

db.product_catalog.find(
  { category: "books", price_krw: { $lt: 30000 } },
  { _id: 1, name: 1, price_krw: 1 }
).sort({ price_krw: 1 });

$lt 意思是“少于”。第二个对象指定返回字段,即投影,1 在 sort() 中表示升序。find() 参考 解释了过滤器和投影。

对于频繁的特定类别价格过滤和排序,可以考虑使用这个复合索引。在生产环境中应用 createIndex() 之前,请检查实际分布和其他查询。

db.product_catalog.createIndex(
  { category: 1, price_krw: 1 }
);

以下命令执行查询并报告执行统计信息,而不更改文档。在 explain("executionStats") 结果中,检查所选择的执行计划、检查的索引键和文档,以及返回的文档。小型集合可能会使用集合扫描;仅仅创建索引并不能显示速度提升。

db.product_catalog.find(
  { category: "books", price_krw: { $lt: 30000 } }
).sort({ price_krw: 1 }).explain("executionStats");

复制和分片的职责不同

副本集是一组维护相同数据的服务器。通常,主节点接受写入操作,副节点复制更改;在发生故障后,有资格的成员可以选举出新的主节点。复制指南 解释了选举和异步复制。

故障转移可能导致临时写入延迟或错误,副节点的读取可能会反映复制延迟。复制并不无条件保证服务不中断或防止所有数据丢失。应将重试机制、重复请求处理以及所需的读/写保证与应用程序结合设计。

分片将数据分布在多个服务器上以实现水平扩展。分片键包含用于数据分布和查询路由的字段。集中式的键或必须访问每个分片的查询可能会限制其好处。《分片指南》解释了键的选择和运维复杂性。

复制和分片可以一起使用,但它们的用途不同。在仅仅因为数据可能增长而引入分片之前,应检查模型、索引和当前瓶颈。采用分片应有理由,否则其额外的复杂性和成本是不值得的。

在采用 MongoDB 之前应检查哪些内容?

  1. 定义重要操作。为产品详情、类别价格搜索和订单创建指定过滤器、排序和更新单元;比较实际的关系型和文档实现。
  2. 审查模型和正确性。决定使用嵌入还是引用、文档大小、验证、索引、事务和即时读取要求。
  3. 比较代表性负载。使用接近实际的数据和请求分布测量延迟、吞吐量和错误。记录版本、硬件和设置,并检查故障和恢复场景。
  4. 选择运维模式。在自我管理和托管服务之间做出决定,分配备份、恢复、监控、升级和支持的责任。
  5. 准备迁移和回退路线。定义标识符/类型转换、缺失数据检查、切换期间的写入以及返回现有存储的条件。

MongoDB Atlas 是一种托管云服务,其运维责任划分不同于自我管理的部署。使用 Atlas 文档 检查所需功能和部署选项。包括存储、备份、网络和运维工作以及实例成本。

对于生产环境,请使用适合您部署的 安全指南 审查最小权限账户、加密连接、网络限制和凭证管理。意外删除可能会传播到副本,因此复制不能替代备份。遵循 备份方法指南 ,并验证恢复确实可行。