Skip to main content

mongo

📅 2026-03-19 ✏️ 2026-04-18 CS INFRA
No related notes

1 · MongoDB#

MongoDB 是一个以 document 为基本单位的数据库。document 是一个自包含的业务对象,通常承载一起读取、一起更新的数据;系统以单文档原子性为核心,再提供索引、复制、分片和事务能力。

用文档模型围绕访问模式建模,用索引优化查询路径,用副本集保证高可用,用分片做水平扩展,并尽量把一致性边界收敛在单文档内。

  • S:业务数据天然以聚合体存在,例如订单和商品列表一起读写,访问模式变化快。
  • C:关系型数据库通常先拆表,再靠 JOIN 拼回对象;schema 变更和对象关系映射成本较高。
  • Q:MongoDB 的核心优势是什么,应该怎么用?
  • A:核心是文档模型。MongoDB 把数据组织成文档,优先让一个业务对象尽量一次存进去、一次取出来。

1.1 · 1. 文档型:围绕访问模式建模#

MongoDB 的出发点不是如何把世界拆成表,而是:

这个数据,是否应该作为一个整体被读取和更新?

文档型数据库会把对象需要的内容装进一个自包含的数据单元里。一个 document 往往就是一个业务聚合体,例如订单和订单项、用户和部分画像信息、文章和标签列表。

所以 MongoDB 的建模起点通常不是 ER 图,而是 access pattern(访问模式):

  • 这个对象最常被怎样查询
  • 哪些字段会一起返回
  • 哪些数据会一起更新
  • 哪些边界需要原子性

对比着看会更直观:

  • MySQL:先把世界拆成表,再靠关系拼回来。
  • MongoDB:先把世界组织成聚合对象,尽量围绕对象本身读写。

灵活 schema 和水平扩展都很重要,但它们本质上是文档模型带来的结果,不是最核心的设计起点。

MongoDB 以 BSON 文档(类 JSON)为基本存储单元,不需要预定义固定列。嵌套子文档和数组可以直接表达一对多关系,因此很多原本依赖 JOIN 的读操作,可以改成一次文档读取完成。

这类模型特别适合:

  • 结构会演进的业务
  • 读写模式变化快的业务
  • 对象天然是聚合体的业务

例如内容管理、用户画像、事件日志等场景,通常都比强关系建模更适合文档模型。

1.1.1 · 1.1 嵌入 vs 引用#

MongoDB 建模的关键决策,通常不是要不要分表,而是嵌入(embed)还是引用(reference)

核心原则:

  • 冗余主要是为了读性能
  • 引用主要是为了一致性和独立演化
场景选择理由
读多写少,数据很少变嵌入(冗余)一次查询拿到全部数据,几乎无一致性问题
数据频繁更新,需保障一致性引用只改一处,避免多处同步
对读延迟极度敏感嵌入(冗余)消除额外查询开销

可以先用两个简单判断做初筛:

  • 一起读、一起写、生命周期一致 -> 倾向 embed
  • 独立变化、共享引用、多对多、会无限增长 -> 倾向 reference

1.2 · 2. 索引:更快的查询路径#

MongoDB 的查询虽然面向 document,但性能瓶颈往往不在能不能查到,而在是精准定位,还是扫了大量文档才查到

索引的作用,就是把查询路径提前组织好,让系统能沿着字段路径快速定位数据,而不是每次都做全集合扫描。

1.2.1 · 2.1 索引在 MongoDB 里的意义#

  • 查询本质上是在文档字段路径上定位数据,例如 statusaddress.city
  • 是否命中合适索引,决定了查询是快速定位还是大量扫描
  • 排序、过滤、分页是否顺畅,也常常取决于索引设计是否贴合访问模式

MongoDB 的索引设计,通常不是给每个常用字段都建一个,而是围绕高频查询模式来设计。

1.2.2 · 2.2 设计索引时看什么#

  • 先看最常见的过滤条件,再看排序条件
  • 高频查询如果总是一起出现,应优先考虑复合索引
  • 低选择性字段单独建索引,收益往往有限
  • 建太多索引会拖慢写入,也会增加存储和维护成本

索引设计本质上是在读性能、写成本和存储开销之间做权衡。

1.2.3 · 2.3 用 explain() 验证是否真的生效#

MongoDB 里建了索引查询真的高效使用了索引不是一回事。判断索引是否有效,最直接的方法是看查询计划。

  • explain() 看查询是否命中了预期索引
  • 关注是否出现大量扫描文档或扫描键的情况
  • 如果查询模式已经稳定,而执行计划仍不理想,通常要回到索引顺序或文档结构重新看

1.3 · 3. 高可用与扩展#

这一层可以拆成两件事:副本集负责,分片负责

1.3.1 · 3.1 副本集(备):解决高可用#

副本集 = 1 个 primary + N 个 secondary,通过复制和选举提供高可用(类raft协议:pv1)

副本集的职责是,不是

  • primary 处理写入
  • secondary 异步复制 oplog
  • primary 宕机后,副本集自动选举新的 primary
  • 可以通过 read preference 把部分读请求分散到 secondary

生产环境里,副本集几乎是默认配置,因为它同时承担了:

  • 高可用
  • 故障切换
  • 数据冗余
  • 一定程度的读扩展

1.3.2 · 3.2 分片(拆):解决容量与写入扩展#

ref: https://www.mongodb.com/docs/manual/sharding ref: https://www.mongodb.com/zh-cn/docs/v6.0/sharding

当单机容量、单副本集写入能力或热点压力成为瓶颈时,MongoDB 通过分片做水平扩展。

分片的职责是,不是

1.3.2.1 · 3.2.1 架构三组件#

组件职责
shard(数据分片)存储数据子集,通常每个分片本身是一个副本集
mongos(查询路由)接收客户端请求,根据分片键路由到目标分片
config server存储分片元数据与 chunk 到分片的映射

1.3.2.2 · 3.2.2 分片键决定数据分布#

  • 分片是在集合级别生效的,把一个集合拆到多个分片
  • 分片键由文档中的一个或多个字段组成
  • 系统会基于分片键把数据切成不重叠的范围,每个范围对应一个 chunk

分片能不能真正带来扩展效果,核心往往取决于 shard key 选得对不对。一个不好的分片键,可能会把写流量集中到少数分片,形成热点。

1.3.2.3 · 3.2.3 常见分片策略#

策略特点
范围分片连续值落在同一分片,利于范围查询;热点风险较高
散列分片哈希打散,写入均匀;但范围查询常需访问更多分片
区域分片按地理或业务规则把数据固定到指定分片

1.3.2.4 · 3.2.4 Chunk 与均衡#

  • chunk 超过阈值后会自动切分
  • balancer 会在分片之间迁移 chunk,保持负载均衡
  • 每个数据库会有一个 primary shard,用来保存该库的未分片集合

1.3.3 · 3.3 副本集 vs 分片#

这两个机制解决的是不同问题:

  • 副本集管:同一份数据复制多份,核心目标是高可用和容灾
  • 分片管:不同数据分散到不同机器,核心目标是容量和写扩展

生产环境里二者通常会组合使用:先把数据拆成多个 shard,再让每个 shard 自己成为一个副本集。

┌─────────────────────────────────────┐
│              mongos (路由)          │
├───────────┬───────────┬─────────────┤
│  Shard A  │  Shard B  │  Shard C    │  ← 分片:数据拆开
│ ┌───────┐ │ ┌───────┐ │ ┌───────┐   │
│ │Primary│ │ │Primary│ │ │Primary│   │
│ │Sec 1  │ │ │Sec 1  │ │ │Sec 1  │   │  ← 副本集:每份复制
│ │Sec 2  │ │ │Sec 2  │ │ │Sec 2  │   │
│ └───────┘ │ └───────┘ │ └───────┘   │
└───────────┴───────────┴─────────────┘

1.4 · 4. 一致性与事务边界#

设计得好的前提下,很多高频业务操作都应当落在一个 document 内完成,因为单文档天然就是事务边界。

MongoDB 最理想的建模状态,是把大多数核心业务写操作压缩在单文档内完成。这样天然具备原子性,不需要额外引入跨文档事务协调。

跨文档事务当然可以用,但更适合当补充机制,而不是默认建模手段。若一个业务动作频繁依赖多文档事务,通常说明文档边界还可以再调整。

1.4.1 · 4.1 运行与稳定性#

除了建模、复制、分片和索引,MongoDB 是否好用还取决于运行时是否可控。

  • 数据安全通常依赖 journaling + 副本集一起保证
  • 运维上要关注连接池配置、慢查询日志、oplog 大小
  • 分片环境下还要持续观察热点、chunk 分布和 balancer 行为