Skip to content

MongoDB 从零到生产级掌握 ​

MongoDB 不能只理解成“能存 JSON 的数据库”。商业项目里真正要掌握的是:为什么文档模型能减少 join,什么时候嵌入什么时候引用,索引为什么能让查询变快,聚合管道怎么一步步处理文档,副本集怎么保证高可用,分片怎么扩展容量,事务为什么不能滥用,线上慢查询和数据倾斜怎么排查。

一句话建立主线:

MongoDB 是文档型数据库,用 BSON 文档组织数据,通过文档建模、索引、聚合、副本集、分片和事务能力支撑灵活结构和高吞吐读写;但它仍然需要围绕查询模式设计文档和索引。

如果你想检查自己是否真的从零基础学懂到能面试、能落地、能排查,按 MongoDB 从零到精通验收清单 逐项验收。

学习目标 ​

学完这一页,你要能做到:

  1. 解释 database、collection、document、field、index 的关系。
  2. 解释 MongoDB 和 MySQL 的建模差异。
  3. 判断数据应该嵌入文档还是拆集合引用。
  4. 写基础 CRUD、条件查询、更新数组、分页和排序。
  5. 解释单字段索引、复合索引、多键索引、唯一索引、TTL 索引。
  6. 看懂 explain("executionStats") 中的关键指标。
  7. 解释聚合管道 $match、$project、$group、$sort、$lookup 的执行思路。
  8. 解释副本集 Primary、Secondary、oplog、选举、读写关注。
  9. 解释分片集群 shard key、mongos、config server、chunk 和数据倾斜。
  10. 解释 MongoDB 事务适用边界。
  11. 能排查慢查询、索引失效、文档过大、分片热点、复制延迟。

为什么需要文档数据库 ​

关系库擅长结构稳定、关系明确、强事务的业务,例如订单、支付、库存、账务。MongoDB 更适合文档天然聚合、字段变化快、嵌套结构明显的业务。

场景关系库做法MongoDB 做法
商品详情商品表、图片表、属性表、规格表多表 join一个商品文档中嵌入详情和有限属性
用户画像多张扩展表或 EAV 模型一个用户画像文档保存灵活字段
埋点事件固定宽表或日志表事件文档保存不同事件字段
数据资产扩展属性关系表频繁加字段扩展属性放嵌套对象

MongoDB 的优势不是“不要设计”,而是“按访问模式设计文档”。不设计的 MongoDB 会比关系库更难维护。

MongoDB 核心结构 ​

mermaid
flowchart TD
    A["Database"] --> B["Collection"]
    B --> C["Document"]
    C --> D["Field"]
    B --> E["Index"]

示例文档:

javascript
{
  _id: ObjectId("..."),
  assetCode: "DATASET_001",
  name: "门诊就诊明细",
  ownerDept: "信息科",
  tags: ["医疗", "门诊", "数据资产"],
  fields: [
    { name: "patient_id", type: "string", sensitive: true },
    { name: "visit_time", type: "date", sensitive: false }
  ],
  createdAt: ISODate("2026-07-05T10:00:00Z")
}

这个文档适合 MongoDB 的原因:

  1. 数据资产详情通常整体读取。
  2. 字段列表数量有限。
  3. 扩展字段可能变化。
  4. 标签、字段、敏感标识天然是嵌套结构。

嵌入还是引用 ​

mermaid
flowchart TD
    A["要建模的数据"] --> B{"是否经常一起读取"}
    B -- "是" --> C{"数量是否有限"}
    C -- "是" --> D["优先嵌入"]
    C -- "否" --> E["拆集合引用"]
    B -- "否" --> E
    E --> F["通过业务 ID 关联查询"]
关系推荐原因
用户和少量地址嵌入一起读取,数量有限
商品和规格参数嵌入商品详情一起展示
用户和订单引用订单无限增长,生命周期不同
文章和评论看规模少量评论可嵌入,海量评论要拆
数据资产和字段定义通常嵌入字段定义随资产详情一起展示
数据资产和访问日志引用日志无限增长

如果把无限增长数组放进一个文档,会导致:

  1. 文档越来越大,接近 16MB 限制。
  2. 更新数组越来越慢。
  3. 单文档热点严重。
  4. 很难按数组元素做复杂分页。

CRUD Demo ​

插入:

javascript
db.data_asset.insertOne({
  assetCode: "DATASET_001",
  name: "门诊就诊明细",
  ownerDept: "信息科",
  status: "PUBLISHED",
  tags: ["医疗", "门诊"],
  fields: [
    { name: "patient_id", type: "string", sensitive: true },
    { name: "visit_time", type: "date", sensitive: false }
  ],
  createdAt: new Date()
})

查询:

javascript
db.data_asset.find({
  ownerDept: "信息科",
  status: "PUBLISHED",
  tags: "医疗"
}, {
  assetCode: 1,
  name: 1,
  ownerDept: 1,
  createdAt: 1
}).sort({ createdAt: -1 }).limit(20)

更新嵌套数组:

javascript
db.data_asset.updateOne(
  { assetCode: "DATASET_001", "fields.name": "patient_id" },
  { $set: { "fields.$.sensitive": true } }
)

分页建议:

javascript
db.data_asset.find({
  status: "PUBLISHED",
  createdAt: { $lt: ISODate("2026-07-05T10:00:00Z") }
}).sort({ createdAt: -1 }).limit(20)

大集合不建议深分页 skip 很大,因为前面的数据仍然要扫描跳过。更适合基于时间或 _id 的游标分页。

一次查询怎么执行 ​

mermaid
flowchart TD
    A["Driver 发送查询"] --> B["mongod 解析 filter/sort/projection"]
    B --> C["查询优化器选择计划"]
    C --> D{"是否有合适索引"}
    D -- "是" --> E["IXSCAN 扫索引"]
    E --> F["FETCH 读取文档"]
    D -- "否" --> G["COLLSCAN 扫集合"]
    F --> H["过滤、排序、投影"]
    G --> H
    H --> I["返回结果"]

explain("executionStats") 重点看:

指标含义判断
winningPlan最终选择的执行计划是否 IXSCAN
totalKeysExamined扫描索引条目数越接近返回数量越好
totalDocsExamined扫描文档数过大说明过滤效率差
nReturned返回文档数和扫描量对比
executionTimeMillis执行耗时是否异常
COLLSCAN集合扫描大集合高风险
SORT内存排序可能需要排序索引

示例:

javascript
db.data_asset.find({
  ownerDept: "信息科",
  status: "PUBLISHED"
}).sort({ createdAt: -1 }).explain("executionStats")

复合索引怎么设计 ​

索引不是字段越多越好,而是匹配查询模式。

常见查询:

javascript
db.data_asset.find({
  ownerDept: "信息科",
  status: "PUBLISHED"
}).sort({ createdAt: -1 })

索引:

javascript
db.data_asset.createIndex({
  ownerDept: 1,
  status: 1,
  createdAt: -1
})

为什么这样排:

  1. ownerDept、status 是等值过滤,放前面。
  2. createdAt 用于排序,放后面。
  3. 查询条件和排序方向能利用同一个复合索引。

常见索引类型:

索引作用场景
单字段索引按一个字段查询assetCode
复合索引多条件过滤和排序ownerDept + status + createdAt
多键索引数组字段索引tags
唯一索引防重复assetCode
TTL 索引自动过期临时 token、短期日志

多键索引要注意数组字段过多会让索引膨胀,写入成本变高。

聚合管道全过程 ​

聚合管道像流水线,一段一段处理文档。

mermaid
flowchart TD
    A["原始文档"] --> B["$match 过滤"]
    B --> C["$project 裁剪字段"]
    C --> D["$group 分组统计"]
    D --> E["$sort 排序"]
    E --> F["$limit 限制数量"]

统计每个科室已发布资产数量:

javascript
db.data_asset.aggregate([
  { $match: { status: "PUBLISHED" } },
  { $group: { _id: "$ownerDept", total: { $sum: 1 } } },
  { $sort: { total: -1 } },
  { $limit: 20 }
])

优化原则:

  1. $match 尽量放前面,减少后续文档数量。
  2. $project 尽早裁剪无用字段。
  3. $sort 尽量利用索引。
  4. $lookup 要控制规模,关联字段建索引。
  5. 大报表考虑预聚合,不要每次扫全量。

副本集原理 ​

副本集解决高可用和数据冗余。

mermaid
flowchart TD
    A["Client 写入"] --> B["Primary"]
    B --> C["写 oplog"]
    C --> D["Secondary 1 拉取 oplog"]
    C --> E["Secondary 2 拉取 oplog"]
    D --> F["重放操作"]
    E --> G["重放操作"]

Primary 故障时会选举:

mermaid
flowchart TD
    A["Primary 故障"] --> B["Secondary 发现心跳异常"]
    B --> C["发起选举"]
    C --> D["多数节点投票"]
    D --> E["新 Primary 产生"]
    E --> F["客户端重新路由写入"]

关键概念:

概念含义
Primary接收写入的主节点
Secondary复制 oplog 的从节点
oplog复制操作日志
writeConcern写入确认级别
readConcern读取一致性级别
readPreference读主还是读从

如果读从节点,要接受复制延迟带来的短暂旧数据风险。

分片集群原理 ​

分片解决单机容量和吞吐瓶颈。

mermaid
flowchart TD
    A["Client"] --> B["mongos 路由"]
    B --> C["Config Server 路由元数据"]
    B --> D["Shard 1"]
    B --> E["Shard 2"]
    B --> F["Shard 3"]

分片键非常关键。选错会出现:

问题表现
低基数很多数据落到少数分片
单调递增新写入集中到一个分片
查询不带分片键广播到所有分片
热点 key单个分片 CPU/IO 很高

示例:

javascript
sh.shardCollection("asset_db.data_asset", { ownerDept: 1, assetCode: 1 })

真实生产要根据数据量、查询条件、写入分布评估,不能随便拿自增 ID 当分片键。

MongoDB 事务边界 ​

MongoDB 支持多文档事务,但它不是鼓励你把关系库模型照搬过来。

适合事务:

  1. 少量文档。
  2. 短事务。
  3. 明确业务边界。
  4. 偶尔需要跨集合一致。

不适合事务:

  1. 大批量长事务。
  2. 跨大量分片事务。
  3. 高频核心交易强一致链路。
  4. 本来可以通过文档嵌入解决的更新。

事务 Demo:

javascript
const session = db.getMongo().startSession()
session.startTransaction()

try {
  const asset = session.getDatabase("asset_db").data_asset
  const audit = session.getDatabase("asset_db").asset_audit

  asset.updateOne(
    { assetCode: "DATASET_001" },
    { $set: { status: "PUBLISHED" } }
  )

  audit.insertOne({
    assetCode: "DATASET_001",
    action: "PUBLISH",
    createdAt: new Date()
  })

  session.commitTransaction()
} catch (e) {
  session.abortTransaction()
  throw e
}

商业场景 ​

数据资产扩展属性 ​

医疗数据资产平台中,不同数据集字段结构差异很大,扩展属性经常变化。可以用 MongoDB 保存资产详情扩展:

  1. MySQL 保存核心主数据:资产 ID、编码、状态、权限。
  2. MongoDB 保存扩展详情:字段列表、标签、血缘、展示配置。
  3. Elasticsearch 保存搜索索引。

这样做的原因:

  1. 核心交易状态仍用关系库保证一致性。
  2. 灵活结构交给 MongoDB。
  3. 全文检索交给 ES。
  4. 不把一个数据库强行用于所有场景。

埋点和日志事件 ​

事件字段经常变化,MongoDB 可以保存原始事件文档。但查询必须设计好索引,例如:

javascript
db.event_log.createIndex({ eventType: 1, createdAt: -1 })
db.event_log.createIndex({ userId: 1, createdAt: -1 })

如果只写不管,集合会快速膨胀。要考虑归档、TTL、冷热分层和聚合统计。

线上排查流程 ​

mermaid
flowchart TD
    A["MongoDB 线上问题"] --> B{"表现"}
    B -- "查询慢" --> C["explain 看 IXSCAN/COLLSCAN"]
    B -- "写入慢" --> D["看索引数量、锁、磁盘、writeConcern"]
    B -- "复制延迟" --> E["看 oplog、Secondary 延迟"]
    B -- "分片倾斜" --> F["看 shard key 和 chunk 分布"]
    B -- "内存高" --> G["看工作集、索引大小、聚合"]
    C --> H["totalDocsExamined 是否远大于 nReturned"]

常用命令:

javascript
db.data_asset.find({ status: "PUBLISHED" }).explain("executionStats")
db.data_asset.getIndexes()
db.currentOp()
rs.status()
sh.status()
db.serverStatus()

排查表:

现象可能原因处理
查询慢无索引、索引顺序不对、内存排序建复合索引,用 explain 验证
写入慢索引太多、磁盘慢、writeConcern 高精简索引,检查磁盘
文档更新慢文档过大、数组无限增长拆集合,控制文档大小
从库读旧数据复制延迟读主或调整一致性要求
分片热点shard key 单调或低基数重新评估分片键
聚合慢$match 太晚、$lookup 大调整管道和预聚合

面试标准回答 ​

MongoDB 适合什么场景 ​

text
MongoDB 适合文档天然聚合、字段变化较快、嵌套结构明显、读写吞吐较高的场景,比如商品详情、用户画像、内容草稿、日志事件、数据资产扩展属性。它不适合大量复杂 join、强事务和高度关系化的核心交易场景。MongoDB 不是随便存 JSON,仍要按查询模式设计文档和索引。

嵌入和引用怎么选 ​

text
经常一起读取、生命周期接近、数量有限的数据适合嵌入;不经常一起读取、数量无限增长、生命周期不同的数据适合拆集合引用。比如用户地址可以嵌入,用户订单不适合无限追加到用户文档里。

MongoDB 查询为什么快 ​

text
MongoDB 查询快主要依赖合适的索引和合理的文档模型。有索引时可以通过 B-Tree 索引快速定位候选文档,再 FETCH 文档;如果经常一起读取的数据被嵌入在同一文档里,也能减少关系库多表 join 的成本。但没有合适索引时同样会 COLLSCAN,性能会很差。

副本集和分片区别 ​

text
副本集解决高可用和数据冗余,Primary 写入,Secondary 复制 oplog,Primary 故障后选举新主。分片解决容量和吞吐扩展,通过 shard key 把数据分布到多个 shard。副本集关注可用性,分片关注水平扩展。

关联知识点 ​

知识点继续学习
MongoDB 总览开始
从零到精通验收MongoDB 从零到精通验收清单
商业场景训练营MongoDB 商业场景训练营
MongoDB 基础MongoDB基础
核心全过程原理核心全过程原理
索引设计索引设计
聚合管道聚合管道
MongoDB 高级MongoDB高级
MongoDB 面试MongoDB面试
MySQLMySQL 从零到生产级掌握
ElasticsearchES 从零到生产级掌握

本章小结 ​

MongoDB 学到生产级,重点不是会写 insertOne 和 find,而是能根据查询模式设计文档,能用索引支撑过滤和排序,能用聚合管道处理统计,能理解副本集和分片的边界,能知道事务为什么不能滥用,并且能用 explain、oplog、分片状态和监控指标定位线上问题。