MongoDB高级
MongoDB 高级部分重点关注索引、聚合、复制集和分片。文档数据库的灵活性很强,但如果索引和文档结构设计不好,也会出现严重性能问题。
为什么高级能力很重要
MongoDB 入门 CRUD 很简单,但真正上线后问题通常出在高级能力:查询不走索引、聚合扫描太多数据、主节点故障后不会切换、分片键选错导致热点。高级能力解决的是“数据量变大、并发变高、机器故障后还能不能稳定运行”的问题。
知识结构
flowchart TD
A["文档设计"] --> B["索引"]
B --> C["聚合管道"]
C --> D["复制集"]
D --> E["分片集群"]
E --> F["事务"]
F --> G["备份和监控"]索引
MongoDB 的索引用于提高查询效率。常见索引类型:
- 单字段索引。
- 复合索引。
- 多键索引,适用于数组字段。
- TTL 索引,适合自动过期数据。
- 文本索引,适合简单全文检索。
复合索引也需要关注字段顺序,通常把等值查询字段放前面,范围查询字段放后面。
索引执行过程
flowchart TD
A["find 条件"] --> B{"是否有合适索引"}
B -- "有" --> C["IXSCAN 扫描索引"]
C --> D["FETCH 读取文档"]
D --> E["过滤、排序、返回"]
B -- "没有" --> F["COLLSCAN 扫描集合"]
F --> E索引不是越多越好:
- 每个索引都占磁盘和内存。
- 写入、更新、删除要维护索引。
- 索引字段顺序不匹配查询,可能仍然不好用。
- 低选择性字段单独建索引意义有限。
ESR 原则
复合索引常用 ESR 思路:
| 字母 | 含义 | 示例 |
|---|---|---|
| E | Equality 等值条件 | hospitalId = H001 |
| S | Sort 排序字段 | createdAt desc |
| R | Range 范围条件 | createdAt >= begin |
实际设计要看查询:
db.assets.find({
hospitalId: "H001",
status: "NORMAL",
createdAt: { $gte: ISODate("2026-07-01") }
}).sort({ createdAt: -1 }).limit(20)可考虑:
db.assets.createIndex({
hospitalId: 1,
status: 1,
createdAt: -1
})hospitalId、status 是等值过滤,createdAt 同时服务范围和排序。
多键索引
数组字段建索引会形成多键索引:
db.assets.createIndex({ tags: 1 })
db.assets.find({ tags: "vip" })如果一个文档数组很大,索引项也会很多,写入和更新成本会上升。标签数组可以,日志数组不可以无限增长。
TTL 索引
TTL 索引用于自动过期删除文档,适合验证码、临时 token、短期日志。
db.verify_codes.createIndex(
{ expireAt: 1 },
{ expireAfterSeconds: 0 }
)注意:
- TTL 删除不是精确到秒实时删除。
- 不适合核心业务数据自动删除。
- 重要数据应先归档再清理。
聚合管道
聚合管道用于数据处理和统计,常见阶段:
$match:过滤数据。$project:选择或计算字段。$group:分组统计。$sort:排序。$limit:限制数量。
flowchart TD
A["原始文档"] --> B["$match 过滤"]
B --> C["$group 分组"]
C --> D["$sort 排序"]
D --> E["统计结果"]核心原理:先减少数据再计算
聚合管道是一段一段处理文档流。越靠前的阶段处理的数据量越大,所以能提前过滤就提前 $match,能提前裁剪字段就 $project。如果先 $group 或 $sort 大量数据,再过滤,内存和 CPU 成本都会明显增加。
聚合优化流程
flowchart TD
A["原始集合"] --> B["$match 尽早过滤"]
B --> C["$project 裁剪字段"]
C --> D["$group 分组计算"]
D --> E["$sort 排序"]
E --> F["$limit 限制结果"]错误写法:
db.asset_collect_logs.aggregate([
{ $group: { _id: "$assetNo", total: { $sum: 1 } } },
{ $match: { total: { $gt: 100 } } }
])如果集合很大,这会先处理全量日志。
更合理的写法:
db.asset_collect_logs.aggregate([
{
$match: {
collectTime: {
$gte: ISODate("2026-07-01"),
$lt: ISODate("2026-07-02")
}
}
},
{ $group: { _id: "$assetNo", total: { $sum: 1 } } },
{ $match: { total: { $gt: 100 } } },
{ $sort: { total: -1 } }
])$lookup 要谨慎
MongoDB 支持 $lookup 做集合关联,但不能把它当成关系库 Join 的完全替代。
db.assets.aggregate([
{ $match: { hospitalId: "H001" } },
{
$lookup: {
from: "departments",
localField: "deptId",
foreignField: "_id",
as: "dept"
}
}
])注意:
- 关联字段要有索引。
$lookup前要先$match缩小主集合。- 高频复杂关联说明文档模型可能设计错了。
- 大报表更适合预聚合或离线计算。
复制集
复制集提供高可用。一个主节点负责写入,多个从节点复制数据。主节点故障后,集群会选举新的主节点。
sequenceDiagram
participant App as 应用
participant P as Primary
participant O as Oplog
participant S as Secondary
App->>P: 写入
P->>O: 记录 oplog
S->>O: 拉取 oplog
S->>S: 重放操作Oplog 是什么
Oplog 是复制集用于同步的操作日志。Primary 写入后把操作记录到 oplog,Secondary 按顺序拉取并重放。
重点:
- Oplog 是有大小限制的循环日志。
- Secondary 落后太久,oplog 已覆盖,就需要重新同步。
- Oplog 延迟会影响读从库的实时性。
- 复制集提高可用性,但不能替代备份。
选举和写关注
Primary 故障后,复制集会选举新的 Primary。写入确认由 writeConcern 控制。
| writeConcern | 含义 | 适用 |
|---|---|---|
{ w: 1 } | Primary 写成功即返回 | 性能高,风险较高 |
{ w: "majority" } | 多数节点确认 | 核心数据更稳 |
{ w: "majority", j: true } | 多数确认并写 journal | 更强持久性 |
如果业务是订单、资产主数据、审计日志,通常不能只追求低延迟,要考虑写入可靠性。
读偏好
读可以配置 readPreference,例如读 Primary 或 Secondary。但读 Secondary 可能读到旧数据。
| readPreference | 特点 |
|---|---|
| primary | 读主,数据最新 |
| secondary | 读从,可能延迟 |
| primaryPreferred | 优先主,主不可用读从 |
不要把从库读当成无成本扩容。复制延迟会让用户刚写完看不到最新数据。
分片
当单机容量或吞吐不足时,可以使用分片集群。分片的关键是选择合适的 shard key。错误的 shard key 会导致数据倾斜或热点写入。
flowchart TD
A["应用请求"] --> B["mongos 路由"]
B --> C["Config Server 路由元数据"]
B --> D["Shard 1"]
B --> E["Shard 2"]
B --> F["Shard 3"]shard key 怎么选
好的 shard key 要同时考虑:
- 写入是否均匀。
- 查询是否能带上 shard key。
- 数据是否容易按 shard key 分布。
- 是否会出现单点热点。
- 是否会导致大量跨分片查询。
| shard key | 风险 |
|---|---|
| 自增时间字段 | 新写入集中到一个分片,形成热点 |
| 低基数字段,如 status | 数据分布严重倾斜 |
| 随机值 | 写入均匀,但范围查询差 |
tenantId + hash | 多租户较常见,但要看查询模式 |
分片前要问的问题
- 单机容量真的不够了吗?
- 是否先优化索引、模型、冷热分离?
- 查询是否大多能带 shard key?
- 运维是否具备分片集群能力?
- 备份、恢复、监控、扩容方案是否准备好?
分片能解决水平扩展,但会显著增加复杂度。不要把分片当成性能问题的第一解。
事务
MongoDB 支持多文档事务,但不应该滥用。它的文档模型本来就是为了把强一致修改尽量收敛在一个文档内。
适合事务:
- 少量文档。
- 短事务。
- 明确业务边界。
- 需要同时修改主数据和审计数据。
不适合事务:
- 批量长事务。
- 跨大量分片。
- 大规模报表更新。
- 本该用关系库表达的复杂强关系。
事务示意:
const session = db.getMongo().startSession()
session.startTransaction()
try {
const assets = session.getDatabase("app").assets
const audits = session.getDatabase("app").asset_audits
assets.updateOne(
{ assetNo: "A-001", status: "WAIT_CHECK" },
{ $set: { status: "CHECKED", checkedAt: new Date() } }
)
audits.insertOne({
assetNo: "A-001",
action: "CHECK",
createdAt: new Date()
})
session.commitTransaction()
} catch (e) {
session.abortTransaction()
throw e
}备份和监控
备份不是“有副本集就够了”。副本集会同步误删和误更新,所以仍然要备份。
| 能力 | 解决什么 | 不能解决什么 |
|---|---|---|
| 副本集 | 节点故障、高可用 | 误删恢复 |
| 分片 | 容量和吞吐扩展 | 模型和索引问题 |
| 备份 | 误删、灾难恢复 | 实时高可用 |
| 监控 | 发现慢查询、延迟、容量 | 自动修复设计错误 |
监控重点:
- 慢查询。
- 复制延迟。
- Oplog 窗口。
- 连接数。
- 内存和磁盘 IO。
- 索引命中和集合扫描。
- 分片数据倾斜。
使用建议
- 根据查询设计索引,不要只根据字段设计索引。
- 聚合前尽量先
$match减少数据量。 - 大集合必须关注慢查询和索引命中。
- 分片前先确认业务真的需要,分片会增加运维复杂度。
- 重要数据要设计备份和恢复方案。
命令 Demo:索引和聚合
创建复合索引:
db.article.createIndex({ category: 1, createdAt: -1 })查询并查看执行计划:
db.article.find({
category: "database"
}).sort({
createdAt: -1
}).limit(10).explain("executionStats")按分类统计文章数:
db.article.aggregate([
{ $match: { status: "published" } },
{ $group: { _id: "$category", total: { $sum: 1 } } },
{ $sort: { total: -1 } }
])$match 尽量放在前面,可以减少后续管道要处理的数据量。
商业场景:资产采集平台怎么用 MongoDB
场景:资产主数据放 MongoDB,采集日志持续增长。
设计:
db.assets.createIndex({ assetNo: 1 }, { unique: true })
db.assets.createIndex({ hospitalId: 1, status: 1, createdAt: -1 })
db.asset_collect_logs.createIndex({ assetNo: 1, collectTime: -1 })
db.asset_collect_logs.createIndex({ collectTime: 1 }, { expireAfterSeconds: 60 * 60 * 24 * 180 })解释:
- 资产主数据按
assetNo唯一。 - 资产列表按医院、状态、时间查询。
- 采集日志按资产和时间查询最近记录。
- 采集日志保留半年可用 TTL,但核心审计不能随便 TTL 删除。
- 如果日志量继续增长,优先考虑冷热拆分和归档,再考虑分片。
面试标准回答
MongoDB 高级能力主要包括索引、聚合管道、复制集、分片、事务、备份和监控。索引要按查询模式设计,复合索引要关注等值、排序和范围字段顺序,并用 explain 验证 totalKeysExamined、totalDocsExamined 和 nReturned。聚合管道要尽早 $match、$project,避免全量 $group、$sort。复制集通过 Primary 写入、oplog 同步和选举提供高可用;分片通过 shard key 把数据分布到多个 shard,但 shard key 选错会导致热点、倾斜和广播查询。MongoDB 支持多文档事务,但应优先通过文档建模减少事务需求。副本集不能替代备份,生产还要监控慢查询、复制延迟、oplog 窗口、连接数和分片倾斜。