这是《MongoDB 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 MongoDB 是面向文档的分布式数据库。它用 BSON 文档保存数据,用集合组织文档,用丰富的索引和聚合管道提供查询分析能力,并通过副本集和分片提供高可用与横向扩展。
MongoDB 不是“比 MySQL 更随意的数据库”。好的 MongoDB 设计仍然需要明确实体边界、访问模式、一致性要求、索引策略和分片键。
1.1 为什么需要文档数据库
一个商品有多组属性:
{
"sku_id": "sku_10001",
"title": "轻薄笔记本",
"price": 5999,
"attrs": [
{ "name": "CPU", "value": "16核" },
{ "name": "内存", "value": "32GB" }
],
"images": ["a.jpg", "b.jpg"],
"tags": ["轻薄", "高续航"]
}
如果用关系表建模,会拆成商品表、属性表、图片表和标签表。对于强事务、强约束的数据,这很合理;但对于读多写少、结构变化快、整体读取的文档,拆表会增加复杂度。
MongoDB 的优势:
- 文档结构贴近业务对象;
- 数组和嵌套对象可自然表达;
- Schema 更灵活,支持字段演进;
- 单文档 ACID 事务;
- 聚合管道表达能力强;
- 原生支持水平分片;
- JSON 开发体验友好。
1.2 MongoDB 的核心概念
| 关系型术语 | MongoDB 术语 | 说明 |
|---|---|---|
| Database | Database | 数据库 |
| Table | Collection | 集合 |
| Row | Document | 文档 |
| Column | Field | 字段 |
| Primary Key | _id |
文档唯一标识 |
| Join | $lookup / Embedded |
关联或内嵌 |
| Index | Index | 索引 |
一个文档示例:
db.products.insertOne({
sku_id: "sku_10001",
title: "轻薄笔记本",
price: NumberDecimal("5999.00"),
attrs: [
{ name: "CPU", value: "16核" },
{ name: "内存", value: "32GB" }
],
status: "ON_SALE",
created_at: new Date()
});
1.3 BSON 与 JSON
MongoDB 在网络上常用 JSON,在存储层使用 BSON。
BSON 是二进制 JSON,支持:
- 整数、浮点数、Decimal128;
- Date;
- ObjectId;
- Binary;
- RegExp; -嵌套对象和数组;
- 内嵌文档。
{
_id: ObjectId("66cb0c7f9e2a1f2b3c4d5e6f"),
price: NumberDecimal("19.90"),
created_at: ISODate("2026-08-25T10:00:00Z"),
binary_field: BinData(0, "aGVsbG8=")
}
金额不要直接用 double,应使用 NumberDecimal。
1.4 嵌套与引用
文档建模的核心选择:
| 方式 | 特点 | 适合 |
|---|---|---|
| 内嵌 Embedded | 一次读取完整对象 | 一对少、整体读取 |
| 引用 Reference | 分开存储再关联 | 一对多、多对多、大对象 |
| 混合 | 常用部分内嵌,历史部分引用 | 复杂业务 |
内嵌示例
{
user_id: "u_1001",
name: "Alice",
addresses: [
{ type: "home", city: "Shanghai" },
{ type: "work", city: "Beijing" }
]
}
适合:地址数量有限,查询用户时通常一起返回。
引用示例
// users
{ _id: "u_1001", name: "Alice" }
// orders
{
order_id: "o_10001",
user_id: "u_1001",
amount: NumberDecimal("199.00")
}
适合:订单数量无限增长,不能全部内嵌到用户文档。
建模原则:
- 按查询建模,而不是按实体关系机械翻译;
- 避免无界数组;
- 大字段和低频字段考虑拆分;
- 高频更新的计数不要放在大文档里;
- 单文档大小上限通常按 16MB 设计,但生产应远小于该值。
1.5 MongoDB 架构概览
单机 MongoDB 由 mongod 进程提供服务,生产环境通常使用副本集。
Replica Set
|-- Primary 接收写入
|-- Secondary 1 同步数据,可提供读
|-- Secondary 2 同步数据,提高可用性
+-- Arbiter 可选,只参与投票不存数据
当 Primary 不可用时,具备投票权的节点会选出新 Primary。
分片集群
Client
-> mongos Router
-> Shard 1 Replica Set
-> Shard 2 Replica Set
-> Shard 3 Replica Set
Config Server
| 组件 | 职责 |
|---|---|
| mongos | 路由请求,不存业务数据 |
| Shard | 存储数据分片 |
| Config Server | 保存集群元数据和 chunk 信息 |
| Shard Key | 决定数据分布 |
1.6 读写关注
MongoDB 提供可调的一致性语义。
Write Concern
db.orders.insertOne(
{ order_id: "o_10001", amount: 100 },
{ writeConcern: { w: "majority", wtimeout: 3000 } }
);
| 级别 | 含义 |
|---|---|
| w: 1 | Primary 确认 |
| w: majority | 多数节点确认 |
| j: true | 落 journal |
关键交易数据建议使用 majority 写关注。
Read Concern
控制读取的数据一致性级别,常见包括 local、majority、linearizable。
Read Preference
控制读请求路由:
| 模式 | 说明 |
|---|---|
| primary | 只读 Primary |
| primaryPreferred | 优先 Primary |
| secondary | 只读 Secondary |
| secondaryPreferred | 优先 Secondary |
| nearest | 最低延迟节点 |
Secondary 读可能带来复制延迟,需要结合业务容忍度。
1.7 MongoDB 与 MySQL 的边界
| 维度 | MongoDB | MySQL |
|---|---|---|
| 数据模型 | 文档 | 关系表 |
| Schema | 灵活 | 强约束 |
| 事务 | 多文档事务可用但成本高 | 强项 |
| 关联 | 内嵌或 $lookup |
JOIN |
| 扩展 | 原生分片成熟 | 常依赖中间件或架构拆分 |
| 金额与强一致 | 可用但需谨慎 | 更常用 |
| 迭代速度 | 字段演进快 | DDL 成本更高 |
适合 MongoDB:
- 用户画像;
- 商品详情;
- 内容管理;
- 行为事件;
- IoT 数据;
- 移动端离线同步;
- 多变的业务配置和元数据。
更适合 MySQL:
- 订单交易;
- 账户余额;
- 支付流水;
- 强约束主数据;
- 复杂财务对账。
1.8 生产使用红线
- 生产必须副本集部署,不能单点;
- 分片键必须提前设计,事后改造成本高;
- 每个查询都要有索引支撑;
- 避免无界数组无限增长;
- 金额使用 Decimal128;
- 多文档事务要短小;
- Secondary 读必须监控复制延迟;
- 必须启用认证、网络隔离和 TLS;
- 备份必须可恢复并定期演练;
- 慢查询和锁等待要有监控。
本章小结
MongoDB 以文档模型、灵活 Schema、聚合管道、副本集和分片见长。它的设计重点是“按访问模式建模”:确定读取形态,再决定内嵌或引用;确定一致性要求,再选择读写关注;确定规模和热点,再设计分片键。它不是关系型数据库的替代品,而是业务模型适配的另一种选择。
思考题
- 什么数据适合内嵌,什么数据适合引用?
- MongoDB 的副本集和分片分别解决什么问题?
- 为什么无界数组是危险设计?
- Secondary 读会带来什么一致性风险?
- 如果让你把商品详情从 MySQL 迁到 MongoDB,如何设计文档结构?