跳转到内容

MongoDB 面试题

15 道题
分类
数据库
题目数
15 道
已阅读 0 / 15 题
1 MongoDB 的核心架构与文档数据模型

答案:

MongoDB 是一款基于文档(Document)的分布式 NoSQL 数据库,采用 BSON(Binary JSON)作为数据存储格式,天然支持灵活 Schema 与嵌套结构,适用于半结构化与快速迭代的业务场景。

核心进程模型:

graph TD
    Client["Client Driver
(mongosh / Driver)"] mongos["mongos
Query Router"] Config["Config Server
(副本集)"] Shard1["Shard-1 (Replica Set)"] Shard2["Shard-2 (Replica Set)"] Shard3["Shard-3 (Replica Set)"] Client -->|"读/写"| mongos mongos -->|"查询元数据"| Config mongos -->|"路由请求"| Shard1 mongos -->|"路由请求"| Shard2 mongos -->|"路由请求"| Shard3

核心组件职责:

组件职责部署形态
mongod数据库核心进程,负责数据存储、复制、查询执行单节点 / 复制集成员 / 分片成员
mongos查询路由器,接收客户端请求并路由至对应 Shard无状态,建议多实例部署
Config Server存储集群元数据(分片位置、Chunk 范围、用户权限)必须部署为复制集(CSRS)
Replica Set一组保持相同数据集的 mongod 节点,提供高可用与读扩展Primary + Secondary + Arbiter
Sharded Cluster数据按 Shard Key 水平拆分到多个 Replica Setmongos + Config Server + N 个 Shard

BSON 与文档结构:

// 一个典型的 BSON 文档示例
{
  _id: ObjectId("507f1f77bcf86cd799439011"),  // 主键,12 字节:时间戳(4) + 机器(3) + PID(2) + 计数器(3)
  user_id: 10001,
  username: "zhangsan",
  profile: {                                    // 嵌套文档
    real_name: "张三",
    age: 28,
    address: {
      city: "Beijing",
      district: "Haidian"
    }
  },
  tags: ["vip", "tech", "finance"],             // 数组
  created_at: ISODate("2024-01-15T08:30:00Z"),
  balance: NumberDecimal("1234.56")             // Decimal128 精确数值
}

BSON 类型系统:

类型说明使用场景
StringUTF-8 字符串文本字段
Int32 / Int64 / Double数值类型计数、金额
Decimal12834 位十进制精度财务金额,避免浮点误差
ObjectId12 字节唯一 ID默认 _id
Date毫秒级时间戳时间字段
Binary二进制数据文件、加密数据
Object嵌套文档复杂结构
Array数组一对多内嵌
Boolean / Null布尔与空值标志位
Timestamp内部时间戳oplog 内部使用

文档模型与关系模型对比:

维度关系模型(MySQL)文档模型(MongoDB)
数据单位行(Row),固定 Schema文档(Document),动态 Schema
关联方式JOIN(跨表)嵌入(Embed)或引用(Reference)
扩展方式垂直扩展 + 分库分表水平扩展(Sharding 原生)
Schema 变更ALTER TABLE(锁表风险)直接插入新字段(无锁)
事务能力强 ACID4.0+ 多文档 ACID,4.4+ 跨分片事务
适用场景强一致、复杂 JOIN、报表分析半结构化、快速迭代、海量数据

适用场景:

  • 内容管理(CMS):文章、评论、标签的嵌套结构天然适配
  • 物联网(IoT):设备时序数据的高写入吞吐
  • 用户画像 / 商品目录:字段动态、Schema 多变
  • 实时分析:聚合管道 + 变更流实时处理
  • 缓存层:替代 Redis 持久化方案
2 MongoDB 的 CRUD 操作与常用操作符

答案:

MongoDB 提供丰富的 CRUD 操作符,支持复杂条件查询、更新与聚合,所有操作均通过 db.collection.<action>(filter, update, options) 模式调用。

Insert 操作:

// 单文档插入
db.users.insertOne({
  username: "zhangsan",
  email: "[email protected]",
  age: 28,
  status: "active",
  created_at: new Date()
})

// 多文档插入(有序,性能更优)
db.users.insertMany([
  { username: "lisi", age: 25, status: "active" },
  { username: "wangwu", age: 30, status: "inactive" }
], { ordered: false })  // ordered=false 失败不中断后续

// insertOne 返回结构
// {
//   acknowledged: true,
//   insertedId: ObjectId("...")
// }

Query 操作与常用操作符:

// 基础查询
db.users.find({ status: "active", age: { $gte: 18 } })

// 投影:仅返回指定字段
db.users.find(
  { status: "active" },
  { username: 1, email: 1, _id: 0 }
)

// 比较操作符
db.products.find({
  price: { $gt: 100, $lte: 500 },     // 100 < price <= 500
  stock: { $ne: 0 },                    // stock != 0
  category: { $in: ["electronics", "books"] }
})

// 逻辑操作符
db.users.find({
  $and: [
    { age: { $gte: 18 } },
    { $or: [
      { status: "active" },
      { vip_level: { $gt: 3 } }
    ]}
  ]
})

// 元素操作符
db.users.find({
  phone: { $exists: true, $type: "string" },
  tags: { $size: 3 },                   // 数组长度为 3
  profile: { $elemMatch: { key: "city", value: "Beijing" } }
})

// 数组操作符
db.posts.find({ tags: { $all: ["mongodb", "nosql"] } })  // 包含所有元素
db.posts.find({ "comments.author": "zhangsan" })          // 数组元素匹配

Update 操作:

// $set 更新字段
db.users.updateOne(
  { _id: ObjectId("...") },
  {
    $set: { status: "inactive", last_login: new Date() },
    $inc: { login_count: 1 },           // 字段自增
    $push: { tags: "new_tag" },         // 数组追加
    $addToSet: { roles: "admin" },      // 数组去重追加
    $unset: { deprecated_field: "" }    // 删除字段
  }
)

// updateMany 批量更新
db.products.updateMany(
  { category: "books", stock: { $lt: 10 } },
  { $set: { low_stock_alert: true } }
)

// upsert:不存在则插入
db.users.updateOne(
  { username: "newuser" },
  { $set: { email: "[email protected]", created_at: new Date() } },
  { upsert: true }
)

// findAndModify:原子操作并返回文档
db.counters.findAndModify({
  query: { _id: "order_id" },
  update: { $inc: { seq: 1 } },
  new: true,                            // 返回更新后的文档
  upsert: true
})

Delete 操作:

// 删除单个文档
db.users.deleteOne({ _id: ObjectId("...") })

// 批量删除
db.logs.deleteMany({
  created_at: { $lt: new Date(Date.now() - 30 * 24 * 3600 * 1000) }  // 30 天前
})

// 危险操作:删除集合所有文档
db.users.deleteMany({})                // 等同于 TRUNCATE

聚合查询与分页:

// countDocuments vs estimatedDocumentCount
db.users.countDocuments({ status: "active" })     // 精确计数(扫描)
db.users.estimatedDocumentCount()                   // 估算(使用元数据,快)

// 排序、限制、跳过
db.users.find({ status: "active" })
  .sort({ created_at: -1 })              // 降序
  .limit(20)
  .skip(0)

// 基于游标的高效分页(避免 skip 大偏移量)
const cursor = db.users.find({ status: "active" })
  .sort({ _id: 1 })
  .limit(20)
const lastId = cursor[19]._id
const nextPage = db.users.find({
  status: "active",
  _id: { $gt: lastId }                   // 游标分页
}).limit(20)
3 MongoDB 的索引机制与 explain() 执行计划分析

答案:

MongoDB 索引采用 B-Tree 结构(自 3.2 起引入 B+Tree 优化),合理使用索引可将查询性能从秒级降至毫秒级,是性能调优的核心手段。

索引类型全景:

graph TD
    Index["MongoDB 索引体系"]
    Index --> Single["单字段索引"]
    Index --> Compound["复合索引"]
    Index --> Multikey["多键索引
(Multikey)"] Index --> Geo["地理空间索引
(2dsphere/2d)"] Index --> Text["文本索引
(Text)"] Index --> Hashed["哈希索引
(Hashed)"] Index --> TTL["TTL 索引"] Index --> Unique["唯一索引"] Index --> Partial["部分索引"] Index --> Sparse["稀疏索引"] Index --> Wildcard["通配符索引
(4.2+)"]

单字段与复合索引:

// 单字段索引
db.users.createIndex({ email: 1 })       // 1: 升序, -1: 降序
db.users.createIndex({ created_at: -1 })

// 复合索引(遵循 ESR 原则:Equality, Sort, Range)
db.orders.createIndex(
  { user_id: 1, status: 1, created_at: -1 }
)
// 等值 user_id + 等值 status + 范围/排序 created_at 均可命中索引

// 唯一索引
db.users.createIndex({ email: 1 }, { unique: true })

// 部分索引:仅索引部分文档,节省空间
db.orders.createIndex(
  { created_at: -1 },
  { partialFilterExpression: { status: "active" } }
)

// 稀疏索引:跳过 null 字段
db.products.createIndex(
  { discontinued_at: 1 },
  { sparse: true }                       // 不索引 discontinued_at 为空的文档
)

特殊索引:

// TTL 索引:自动过期删除
db.sessions.createIndex(
  { created_at: 1 },
  { expireAfterSeconds: 3600 }            // 1 小时后自动删除
)

// 文本索引:全文搜索
db.articles.createIndex(
  { title: "text", content: "text" },
  { weights: { title: 10, content: 1 } }
)
db.articles.find({ $text: { $search: "mongodb performance" } })

// 多键索引:数组字段自动创建
db.posts.createIndex({ tags: 1 })          // tags 为数组,自动 multikey
db.posts.find({ tags: "mongodb" })        // 命中索引

// 地理空间索引
db.locations.createIndex({ location: "2dsphere" })
db.locations.find({
  location: {
    $near: {
      $geometry: { type: "Point", coordinates: [116.404, 39.915] },
      $maxDistance: 5000
    }
  }
})

// 哈希索引(用于 Shard Key 哈希分片)
db.users.createIndex({ _id: "hashed" })

explain() 执行计划分析:

// 查询执行计划
db.orders.find({ user_id: 10001, status: "active" })
  .sort({ created_at: -1 })
  .explain("executionStats")             // 详细统计

// 关键字段解读

explain 关键指标:

字段含义健康值
winningPlan.stage执行阶段IXSCAN(索引扫描)优于 COLLSCAN(全表扫描)
executionStats.totalDocsExamined扫描文档数越接近 nReturned 越好
executionStats.totalKeysExamined扫描索引键数越接近 nReturned 越好
executionStats.nReturned返回文档数totalDocsExamined 比值即索引效率
executionStats.executionTimeMillis总执行时间业务可接受范围
executionStats.stage是否 SORT 内存排序IXSCAN + 无 SORT 内存为佳
rejectedPlans被拒绝的执行计划多个候选时用于优化器诊断
// 反例:全表扫描 COLLSCAN(性能差)
{
  "winningPlan": {
    "stage": "COLLSCAN",
    "filter": { "user_id": { "$eq": 10001 } }
  },
  "executionStats": {
    "totalDocsExamined": 1000000,
    "nReturned": 1,
    "executionTimeMillis": 850
  }
}

// 正例:索引扫描 IXSCAN(性能优)
{
  "winningPlan": {
    "stage": "FETCH",
    "inputStage": {
      "stage": "IXSCAN",
      "indexName": "user_id_1_status_1_created_at_-1"
    }
  },
  "executionStats": {
    "totalKeysExamined": 1,
    "totalDocsExamined": 1,
    "nReturned": 1,
    "executionTimeMillis": 2
  }
}

索引优化策略:

策略说明
ESR 原则复合索引字段顺序:Equality → Sort → Range
覆盖查询索引包含查询所需全部字段,避免回表
索引选择性高基数(Cardinality)字段放前面
避免索引数组过大多键索引对数组内每个元素都建立索引
监控索引使用db.collection.aggregate([{$indexStats:{}}]) 查看索引命中次数
删除冗余索引重复前缀索引、命中率低的索引及时清理

覆盖查询示例:

// 创建覆盖索引
db.orders.createIndex({ user_id: 1, status: 1, total: 1 })

// 查询完全命中索引(无需回表)
db.orders.find(
  { user_id: 10001, status: "active" },
  { user_id: 1, status: 1, total: 1, _id: 0 }    // 仅投影索引字段
)
// executionStats.stage = "IXSCAN"(无 FETCH 阶段),性能最佳
4 WiredTiger 存储引擎原理与 B+Tree

答案:

WiredTiger 是 MongoDB 3.2 起的默认存储引擎,采用 B+Tree 索引结构与 LSM 思想的数据组织方式,结合文档级并发控制(Document-Level Concurrency),提供高并发写入与压缩能力。

WiredTiger 核心架构:

graph TD
    Client["Client Query"]
    
    Client --> WT["WiredTiger Engine"]
    
    subgraph WT["WiredTiger Storage Engine"]
        BTree["B+Tree 索引
(内存 + 磁盘)"] RowStore["Row Store
变长记录存储"] Block["Block Manager
(WT_BLOCK)"] Cache["Cache Pool
(默认 50% RAM - 1GB)"] Evict["Eviction
(LRU/K)"] Checkpoint["Checkpoint
(默认 60s)"] Log["WiredTiger Log
(WAL)"] BTree --> Cache RowStore --> Cache Cache --> Evict Cache --> Block Block --> Disk["Disk Files
*.wt"] Cache --> Checkpoint Client --> Log Log --> Disk end

B+Tree vs B-Tree:

特性B-TreeB+Tree(WiredTiger)
数据存储位置内部节点 + 叶子节点仅叶子节点
叶子节点链表有(双向链表,支持范围扫描)
内部节点存储数据 + 指针仅存储键 + 子节点指针
范围查询需中序遍历叶子节点顺序扫描,O(n)
磁盘 I/O每次可能读取多个节点内部节点更小,单次 I/O 可加载更多索引项
适用场景点查询点查询 + 范围查询(数据库首选)

WiredTiger 写入流程:

1. 客户端发起 insert/update
2. WiredTiger 修改内存中的 B+Tree(copy-on-write)
3. 写入预写日志(WAL) → journal 文件
4. journal 落盘(fsync)→ 确认写入成功
5. 异步将脏页刷盘(eviction + checkpoint)
6. Checkpoint 创建一致性快照(默认 60 秒一次)

关键配置参数:

# mongod.conf(WiredTiger 配置段)
storage:
  engine: wiredtiger
  wiredTiger:
    engineConfig:
      cacheSizeGB: 8                    # WiredTiger 缓存大小(默认 50% RAM - 1GB)
      journalCompressor: snappy         # WAL 压缩算法(none/snappy/zlib)
      directoryForIndexes: false        # 索引与数据分离目录
      maxCacheOverflowFileSizeGB: 0     # 溢出文件大小(0 表示禁用磁盘溢出)
    collectionConfig:
      blockCompressor: snappy           # 集合数据压缩(none/snappy/zlib)
      indexConfig:
        prefixCompression: true         # 索引前缀压缩
    indexConfig:
      prefixCompression: true

document-level concurrency(文档级锁):

// WiredTiger 使用 MVCC 实现文档级并发控制
// 不同文档的写操作完全并行,无锁竞争
db.orders.updateOne({ _id: 1 }, { $set: { status: "paid" } })
db.orders.updateOne({ _id: 2 }, { $set: { status: "shipped" } })
// 上述两个操作可以并发执行,无需加锁

// 同一文档的并发写会串行化
// WiredTiger 在文档级别检测冲突,必要时短暂等待或重试

WiredTiger 性能调优矩阵:

参数默认值生产建议说明
cacheSizeGB0.5 × RAM - 1系统内存 50%(上限 16GB)越大热点缓存越多
blockCompressorsnappysnappy(速度)/ zlib(压缩率)snappy 速度优先
journalCompressorsnappysnappy减少 WAL 写入量
checkpointSizeMB1024根据数据量调整控制 Checkpoint 期间写入量
eviction_target8080触发 Eviction 阈值
eviction_trigger9595强制 Eviction 阈值
eviction_dirty_target55脏页 Eviction 阈值
eviction_dirty_trigger2020强制脏页 Eviction

WiredTiger 与原 MMAPv1 引擎对比:

维度MMAPv1(3.2 之前)WiredTiger(3.2+)
并发模型库级锁 / 集合级锁文档级 MVCC
压缩不支持snappy / zlib(节省 50%+ 空间)
内存管理mmap(OS 缓存)自管理缓存(更可控)
故障恢复重放 oplogjournal + Checkpoint
空间回收需 repair / resync文档更新不分配新空间
多核扩展差(锁竞争)优(文档级并发)

生产监控指标:

// WiredTiger 状态查看
db.serverStatus().wiredTiger
// 关注指标:
// - cache: "maximum bytes configured" / "bytes currently in the cache"
// - cache: "tracked dirty bytes in the cache"
// - cache: "pages read into cache" / "pages written from cache"
// - cache: "percentage overhead"
// - cache: "eviction server evicting pages"
// - "bytes belonging to page images in the cache"
5 复制集(Replica Set)架构与故障转移机制

答案:

MongoDB Replica Set 是一组维护相同数据集的 mongod 节点,提供数据冗余、高可用与读扩展能力,Primary 故障时自动触发选举(Election)机制选出新 Primary。

复制集架构:

graph TD
    App["Application Driver"]
    App -->|"Read/Write"| Primary["Primary
(可读写)"] Primary -->|"oplog 复制"| Sec1["Secondary-1
(只读/Hidden)"] Primary -->|"oplog 复制"| Sec2["Secondary-2
(只读/Hidden)"] Primary -->|"oplog 复制"| Sec3["Secondary-3
(Arbiter)"] subgraph Election["选举机制"] Heartbeat["Heartbeat
(每 2s)"] Priority["Priority 选举优先级"] Vote["多数派投票 (Majority)"] end Sec1 -.-> Heartbeat Sec2 -.-> Heartbeat Sec3 -.-> Heartbeat Heartbeat -.-> Vote

节点角色:

角色职责注意事项
Primary接收所有写入操作,维护 oplog 权威副本集群仅 1 个,宕机触发选举
Secondary复制 Primary oplog 并应用,提供只读默认 0 延迟,可调整 Priority
Arbiter投票节点,不存储数据仅在偶数节点时用于打破平局
Hidden不可被客户端读取的 Secondary用于备份 / 报表,不影响 Primary 选举
Delayed延迟应用 oplog 的 Secondary灾难恢复(误删除防护)
Priority 0不参与 Primary 选举的 Secondary通常为多数据中心灾备节点

复制集配置:

// 初始化复制集
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "mongo-1:27017", priority: 2 },     // 高优先级倾向
    { _id: 1, host: "mongo-2:27017", priority: 1 },
    { _id: 2, host: "mongo-3:27017", priority: 1 },
    { _id: 3, host: "mongo-arbiter:27017", arbiterOnly: true }
  ]
})

// 查看复制集状态
rs.status()
// 输出关键字段:
// - state: 1 (PRIMARY) / 2 (SECONDARY) / 7 (ARBITER)
// - members[].stateStr
// - members[].optimeDate: 最后应用的 oplog 时间
// - members[].health: 1 (健康) / 0 (宕机)
// - members[].electionDate: 上次选举时间

// 查看主从延迟
rs.printSecondaryReplicationInfo()
// 输出: "source: mongo-1, syncedTo: ..., lag: 0s"

oplog(操作日志):

oplog 是一组固定大小的 capped collection,存储在 local.oplog.rs,记录所有修改数据的操作。

关键特性:
- 幂等性:oplog 操作可重放(除 oplog 应用本身)
- 幂等写入:所有 oplog 操作都包含 _id
- 时序性:每条操作携带时间戳与唯一 ID
- 容量:默认 5% 磁盘(可通过 oplogSizeMB 调整)
- 复制单位:Secondary 持续拉取 Primary oplog 并应用

故障转移(Failover)流程:

sequenceDiagram
    participant App as Application
    participant P as Primary (mongo-1)
    participant S1 as Secondary-1
    participant S2 as Secondary-2
    
    Note over P: 正常运行
    P->>S1: 复制 oplog
    P->>S2: 复制 oplog
    
    Note over P: T+0s Primary 宕机
    P--xApp: 连接断开
    
    Note over S1,S2: T+10s 检测到 Primary 失联
    S1->>S2: 发起选举
    S2->>S1: 投票
    S1->>S1: 获得多数票(2/3),成为新 Primary
    
    Note over S1: T+12s 自我提升
    S1-->>App: 重新接受写入
    
    Note over P: 旧 Primary 重启
    P->>S1: 以 Secondary 身份加入
    S1->>P: 重新同步 oplog

选举参数配置:

// 复制集配置参数
cfg = rs.conf()

cfg.settings = {
  electionTimeoutMillis: 10000,         // 选举超时(默认 10s)
  heartbeatIntervalMillis: 2000,        // 心跳间隔
  catchUpTimeoutMillis: 2000,           // 新 Primary 追平数据超时
  chainingAllowed: true,                // 允许 Secondary 链式复制(4.0+)
  // 多数派写入控制
  getLastErrorDefaults: { w: 1, wtimeout: 0 },
  // w: "majority" 表示多数节点确认后才返回
  getLastErrorModes: [
    { name: "multi-region", loc: { "$in": ["us", "eu", "ap"] } }
  ]
}

rs.reconfig(cfg)

写入关注(Write Concern):

// 写入确认级别
db.orders.insertOne(
  { user_id: 10001, total: 99.9 },
  { writeConcern: { w: "majority", wtimeout: 5000 } }  // 多数节点确认,5s 超时
)

// 写入关注级别
// w: 0     - 不等待确认(最快,可能丢失)
// w: 1     - Primary 确认(默认)
// w: 2     - 1 Primary + 1 Secondary 确认
// w: "majority" - 多数节点确认(推荐生产)
// w: <number> - 自定义数量

// 读关注(Read Concern)
db.orders.find({ user_id: 10001 })
  .readConcern("majority")              // 读取已确认的多数派数据
  .readPreference("primaryPreferred")   // 优先 Primary,失败读 Secondary

读偏好(Read Preference):

模式行为适用场景
primary仅 Primary 读取强一致读
primaryPreferred优先 Primary,失败时 Secondary默认推荐,兼顾可用性
secondary仅 Secondary 读取报表 / 备份,不影响 Primary
secondaryPreferred优先 Secondary读多写少
nearest网络最近的节点多数据中心部署

生产最佳实践:

实践说明
节点数 >= 3至少 3 数据节点 + 0~2 Arbiter,确保多数派选举
跨可用区部署Primary / Secondary 分布在不同 AZ
隐藏节点备份Hidden Secondary 节点跑备份任务,不影响 Primary
延迟节点防误删Delayed Secondary 延迟 1~24 小时,用于回滚
w: “majority”生产环境使用 majority 写入关注
Oplog 监控监控 oplog window(oplog 可回放时长)
6 分片集群(Sharding Cluster)原理与分片策略

答案:

MongoDB Sharded Cluster 将数据按 Shard Key 水平拆分到多个 Replica Set,实现存储与吞吐的水平扩展,支持千亿级文档与 PB 级数据量。

分片集群架构:

graph TD
    App["Application"]
    
    App --> M1["mongos-1"]
    App --> M2["mongos-2"]
    App --> M3["mongos-3"]
    
    subgraph Config["Config Server (CSRS)"]
        C1["Config-1
(Primary)"] C2["Config-2"] C3["Config-3"] end subgraph Shard1["Shard-1 (Replica Set)"] S1P["Primary"] S1S["Secondary"] end subgraph Shard2["Shard-2 (Replica Set)"] S2P["Primary"] S2S["Secondary"] end subgraph Shard3["Shard-3 (Replica Set)"] S3P["Primary"] S3S["Secondary"] end M1 -->|"元数据查询"| Config M2 -->|"元数据查询"| Config M3 -->|"元数据查询"| Config M1 -->|"读写请求"| S1P M1 -->|"读写请求"| S2P M1 -->|"读写请求"| S3P M2 -->|"读写请求"| S1P M3 -->|"读写请求"| S3P

分片相关概念:

概念说明
Shard存储分片数据的 Replica Set,单个 Shard 可承载数 TB 数据
Config Server集群元数据存储(CSRS,复制集模式),存储 chunk 映射、shard 信息
mongos查询路由器,无状态,内存中缓存 Chunk 路由表
ChunkShard 内连续范围的数据块,默认 128MB
Shard Key用于分片的字段,决定数据分布与查询路由
Balancer后台进程,自动在 Shard 间迁移 Chunk 以均衡数据

分片策略对比:

策略语法数据分布适用场景
范围分片(Range){ user_id: 1 }按字段值范围连续分布范围查询、单调递增字段
哈希分片(Hashed){ user_id: "hashed" }哈希散列,均匀分布写入密集、避免热点
复合分片{ region: 1, user_id: 1 }先按 region 范围,再按 user_id 范围多租户 + 高基数字段
带标签分片{ region: 1 } + Zone按地理 / 业务隔离跨数据中心、GDPR 合规

启用分片:

// 1. 启用分片
sh.enableSharding("mydb")

// 2. 选择 Shard Key 并创建索引
db.orders.createIndex({ user_id: 1 })

// 3. 对集合进行分片
sh.shardCollection(
  "mydb.orders",
  { user_id: 1 }                        // 范围分片
)
// 或
sh.shardCollection(
  "mydb.orders",
  { user_id: "hashed" }                 // 哈希分片
)

// 4. 复合分片
sh.shardCollection(
  "mydb.orders",
  { region: 1, user_id: 1 }             // 先 region,再 user_id
)

Chunk 拆分与迁移:

Chunk 生命周期:

1. 初始:每个 Shard 持有若干 Chunk
2. 拆分(Split):当 Chunk 数据量超过 chunkSize(默认 128MB)触发拆分
3. 迁移(Migrate):Balancer 检测到 Shard 间 Chunk 数量不均衡时
   触发迁移,源 Shard 发送 Chunk 数据至目标 Shard

Chunk 大小配置:
- 默认 128MB(适合大多数场景)
- 调整:db.settings.save({ _id: "chunksize", value: 64 })  // 单位 MB
- 大 Chunk:减少元数据,但拆分不灵活
- 小 Chunk:迁移频繁(容易影响性能)

Balancer 均衡机制:

// 查看 Balancer 状态
sh.getBalancerState()                    // true / false

// 关闭 Balancer(运维窗口)
sh.stopBalancer()

// 启用 Balancer
sh.startBalancer()

// 立即触发一次均衡(默认自动)
sh.startBalancer(timeout, interval)

// 监控 Chunk 分布
db.adminCommand({ listShards: 1 })
use config
db.chunks.aggregate([
  { $group: { _id: "$shard", count: { $sum: 1 } } }
])
// 输出:[{ _id: "shard-1", count: 50 }, { _id: "shard-2", count: 30 }]

Shard Key 选择原则:

原则说明
高基数唯一值数量大(亿级以上),避免 Chunk 拆分困难
低频修改Shard Key 一旦选定不可修改,避免频繁更新 Shard Key
查询命中业务主要查询条件包含 Shard Key,避免 Scatter-Gather
避免单调递增_id / ObjectId 哈希分片可避免写入热点
避免粗粒度状态字段(status)等低基数不适合分片

分片查询路由:

// Targeted Query:包含 Shard Key 的查询,仅查询相关 Shard
db.orders.find({ user_id: 10001 })
// mongos 根据 user_id 路由到对应 Shard

// Broadcast Query:不包含 Shard Key 的查询,所有 Shard 都要查
db.orders.find({ status: "active" })
// mongos 向所有 Shard 广播,结果合并返回(性能差)

// 复合 Shard Key 部分命中
// Shard Key: { region: 1, user_id: 1 }
db.orders.find({ region: "us-east" })           // 部分命中,按 region 路由
db.orders.find({ user_id: 10001 })              // 不可路由,广播查询

生产注意事项:

问题解决方案
Jumbo Chunk(巨型块)拆分阈值受限(如唯一索引),手动 split + 强制迁移
热点 Shard选择哈希分片或高基数 Shard Key
Balancer 影响业务业务低峰期启用,或使用窗口化调度(Balancing Window)
不可分片集合集合超过 16MB(4.4+ 之前为 16MB)会报错
跨分片事务4.4+ 支持分布式事务,但性能与一致性需权衡
config server 性能监控元数据查询延迟,避免元数据成为瓶颈
7 聚合管道(Aggregation Pipeline)

答案:

聚合管道是 MongoDB 的数据分析框架,通过多阶段(Stage)管道对文档进行转换、过滤、统计与连接,类似于 Unix 管道的流式处理。

聚合管道架构:

graph LR
    A["$match
过滤"] --> B["$project
投影"] B --> C["$group
分组聚合"] C --> D["$sort
排序"] D --> E["$limit
限制"] E --> F["$lookup
关联查询"] F --> G["$out / $merge
输出"]

常用聚合阶段:

// 典型聚合管道
db.orders.aggregate([
  // 1. 过滤:仅看 2024 年的订单
  {
    $match: {
      created_at: { $gte: ISODate("2024-01-01"), $lt: ISODate("2025-01-01") },
      status: "completed"
    }
  },
  // 2. 关联查询:获取用户信息
  {
    $lookup: {
      from: "users",
      localField: "user_id",
      foreignField: "_id",
      as: "user_info"
    }
  },
  // 3. 拆解数组:每个 user_info 元素作为独立文档
  { $unwind: "$user_info" },
  // 4. 投影:仅保留必要字段
  {
    $project: {
      _id: 0,
      order_id: "$_id",
      user_id: 1,
      total: 1,
      user_name: "$user_info.username",
      user_city: "$user_info.profile.address.city"
    }
  },
  // 5. 分组聚合:按城市统计 GMV
  {
    $group: {
      _id: "$user_city",
      total_gmv: { $sum: "$total" },
      order_count: { $sum: 1 },
      avg_order: { $avg: "$total" },
      max_order: { $max: "$total" },
      min_order: { $min: "$total" },
      unique_users: { $addToSet: "$user_id" }
    }
  },
  // 6. 计算字段:客单价
  {
    $addFields: {
      unique_user_count: { $size: "$unique_users" }
    }
  },
  // 7. 排序:GMV 降序
  { $sort: { total_gmv: -1 } },
  // 8. 限制:Top 10 城市
  { $limit: 10 }
])

核心 Stage 详解:

Stage功能示例
$match过滤文档(类似 WHERE){ $match: { status: "active" } }
$project字段投影与重命名{ $project: { name: 1, total: { $multiply: ["$price", "$qty"] } } }
$group分组聚合{ $group: { _id: "$category", total: { $sum: 1 } } }
$sort排序{ $sort: { created_at: -1 } }
$limit / $skip分页{ $limit: 20 }
$lookup关联查询(类似 JOIN){ $lookup: { from: "users", ... } }
$unwind拆解数组字段{ $unwind: "$tags" }
$facet多管道并行同时统计不同维度
$bucket分桶按金额区间分组
$graphLookup递归查询树形 / 图结构遍历
$merge / $out输出到集合持久化聚合结果
$addFields / $set增加 / 修改字段{ $addFields: { full_name: { $concat: ["$first", " ", "$last"] } } }

$lookup 关联查询:

// 单条件关联
db.orders.aggregate([
  {
    $lookup: {
      from: "users",                     // 关联集合
      localField: "user_id",             // 本集合字段
      foreignField: "_id",               // 关联集合字段
      as: "user"                         // 输出数组字段名
    }
  }
])

// 嵌套 pipeline 关联(4.0+)
db.orders.aggregate([
  {
    $lookup: {
      from: "users",
      let: { uid: "$user_id" },
      pipeline: [
        { $match: { $expr: { $eq: ["$_id", "$$uid"] } } },
        { $project: { username: 1, city: "$profile.address.city" } }
      ],
      as: "user"
    }
  }
])

// 多条件关联 + 相关子查询
db.orders.aggregate([
  {
    $lookup: {
      from: "products",
      let: { items: "$items" },
      pipeline: [
        { $match: { $expr: { $in: ["$_id", "$$items"] } } },
        { $project: { name: 1, price: 1 } }
      ],
      as: "product_details"
    }
  }
])

$facet 多面分析:

// 单次查询返回多个维度的统计
db.products.aggregate([
  {
    $facet: {
      // 价格区间分布
      price_ranges: [
        {
          $bucket: {
            groupBy: "$price",
            boundaries: [0, 100, 500, 1000, 5000, 10000],
            default: "10000+",
            output: {
              count: { $sum: 1 },
              avg_rating: { $avg: "$rating" }
            }
          }
        }
      ],
      // Top 10 评分
      top_rated: [
        { $sort: { rating: -1 } },
        { $limit: 10 },
        { $project: { name: 1, rating: 1, price: 1 } }
      ],
      // 类别统计
      by_category: [
        {
          $group: {
            _id: "$category",
            count: { $sum: 1 },
            avg_price: { $avg: "$price" }
          }
        },
        { $sort: { count: -1 } }
      ]
    }
  }
])

聚合管道优化:

优化策略说明
$match 前置尽早过滤,减少后续 Stage 处理量
$project 前置减少字段,降低内存占用
利用索引$match / $sort / $group 的字段需建索引
allowDiskUse大数据量时允许使用磁盘临时文件
避免 $unwind 大数组拆解后文档数可能爆炸性增长
分批处理大批量聚合使用 $out / $merge 分批执行
// allowDiskUse 启用磁盘临时文件
db.orders.aggregate(
  [...],
  { allowDiskUse: true }
)

// 聚合管道执行分析
db.orders.explain("executionStats").aggregate([...])
// 关注:
// - $cursor.queryPlanner.winningPlan
// - $cursor.executionStats.totalDocsExamined
// - $cursor.executionStats.executionTimeMillis
8 MongoDB 多文档 ACID 事务

答案:

MongoDB 4.0 引入多文档 ACID 事务(4.2+ 支持跨分片分布式事务),完全支持 ACID 四大事务特性,使 MongoDB 同时具备文档模型的灵活性与传统关系型数据库的事务保障。

事务能力演进:

版本事务能力限制
4.0副本集内多文档 ACID 事务不支持跨分片
4.2跨分片分布式事务性能开销较大
4.4改进事务并发与性能
5.0+性能持续优化

事务 ACID 特性:

特性实现机制
Atomicity(原子性)WiredTiger 的 MVCC + 回滚机制,事务失败时回滚所有修改
Consistency(一致性)WiredTiger 检查约束 + Schema 验证
Isolation(隔离性)Snapshot Isolation(快照隔离),默认读已提交
Durability(持久性)事务提交时强制 journal 刷盘

事务使用方式:

// 方式 1:回调 API(推荐,自动重试 TransientTransactionError)
const session = db.getMongo().startSession()
session.startTransaction({
  readConcern: { level: "snapshot" },
  writeConcern: { w: "majority" },
  readPreference: "primary"
})

try {
  const users = session.getDatabase("mydb").users
  const orders = session.getDatabase("mydb").orders
  
  // 业务逻辑:转账
  users.updateOne(
    { _id: 10001 },
    { $inc: { balance: -100 } },
    { session }
  )
  users.updateOne(
    { _id: 10002 },
    { $inc: { balance: 100 } },
    { session }
  )
  orders.insertOne(
    { from: 10001, to: 10002, amount: 100, created_at: new Date() },
    { session }
  )
  
  session.commitTransaction()
  console.log("事务提交成功")
} catch (error) {
  // 区分可重试错误与不可重试错误
  if (error.hasErrorLabel("TransientTransactionError")) {
    // 临时错误(锁冲突、复制集选举),可重试
    console.log("可重试的事务错误")
  } else if (error.hasErrorLabel("UnknownTransactionCommitResult")) {
    // 提交结果未知(网络问题),可重试提交
    console.log("提交结果不确定,需重试 commit")
  } else {
    // 业务错误,回滚
    console.log("业务错误,事务回滚")
  }
  session.abortTransaction()
} finally {
  session.endSession()
}

// 方式 2:withTransaction 自动重试
session.withTransaction(
  async () => {
    const users = session.getDatabase("mydb").users
    const orders = session.getDatabase("mydb").orders
    
    users.updateOne({ _id: 10001 }, { $inc: { balance: -100 } }, { session })
    users.updateOne({ _id: 10002 }, { $inc: { balance: 100 } }, { session })
    orders.insertOne(
      { from: 10001, to: 10002, amount: 100 },
      { session }
    )
  },
  {
    readConcern: { level: "snapshot" },
    writeConcern: { w: "majority" },
    readPreference: "primary"
  }
)

事务隔离级别:

隔离级别行为适用场景
readConcern: "local"读取节点本地最新数据(默认)低延迟读
readConcern: "majority"读取多数派已确认数据避免脏读
readConcern: "snapshot"事务开始时的快照数据事务可重复读
readConcern: "linearizable"线性化读,强一致极端一致性需求

事务与性能:

事务开销分析:

1. 时间戳管理:
   - 每个事务携带时间戳 t
   - WiredTiger MVCC 通过 t 确定可见性
   - 事务粒度越大,MVCC 维护成本越高

2. 锁竞争:
   - 多文档事务会持有 WiredTiger 内部锁
   - 锁冲突时等待时间随事务时长增加
   - 长事务会导致 WiredTiger cache 压力

3. 网络开销:
   - 事务开始 / 提交时需与所有相关节点通信
   - 跨分片事务需更多节点协调

4. 磁盘 I/O:
   - 事务回滚时产生 undo log
   - 大量小事务的 undo log 累积

事务最佳实践:

实践说明
事务粒度事务应尽量短,仅包含必要操作
避免长事务事务时长建议 < 60 秒,避免锁等待与缓存压力
错误重试实现 TransientTransactionError 错误重试逻辑
合理 readConcern强一致需求用 snapshot,性能优先用 local
避免跨分片事务通过数据建模减少跨分片事务使用
事务超时设置 transactionLifetimeLimitSeconds(默认 60s)
监控事务db.serverStatus().transactions 监控事务状态

事务监控:

// 查看事务状态
db.serverStatus().transactions
// 关键字段:
// - "transactionsCollectionWriterCount"
// - "transactionsCollectionReaderCount"
// - "totalAborted": 已回滚事务数
// - "totalCommitted": 已提交事务数
// - "totalStarted": 总启动事务数
// - "currentActive": 当前活跃事务数
// - "currentInactive": 当前空闲事务数

// 查看活跃事务
db.adminCommand({
  currentOp: 1,
  $or: [
    { "op": "command", "command.$_internalId": { $exists: true } },
    { "op": "command", "command.commitTransaction": { $exists: true } }
  ]
})

事务与原子单文档操作对比:

// MongoDB 单文档操作天然原子(无需事务)
// 适用:嵌入式结构内的字段更新
db.users.updateOne(
  { _id: 10001, balance: { $gte: 100 } },   // 乐观锁条件
  { $inc: { balance: -100 } }                // 条件性扣款
)
// 单文档原子操作性能更优,优先使用

// 多文档事务:跨集合 / 跨文档需要事务
// 如:账户 A 扣款 + 账户 B 加款 + 订单记录
// 必须使用事务保证三者一致
9 Change Streams 变更流与实时数据管道

答案:

Change Streams 是 MongoDB 3.6 引入的功能,基于 oplog 变更提供实时事件流,应用程序可以订阅集合的所有数据变更(insert / update / delete / drop 等),是实现 CDC(Change Data Capture)和实时数据管道的核心机制。

Change Streams 架构:

graph LR
    App["Application"]
    App -->|"watch()"| Mongo["MongoDB"]
    Mongo -->|"订阅 oplog"| Oplog["local.oplog.rs"]
    Oplog -->|"聚合变更事件"| CS["Change Stream"]
    CS -->|"推送事件"| Consumer["Consumer
(Application / Kafka Connector)"] Consumer --> Action["业务处理
数据同步/缓存更新/触发器"]

Change Stream 监听示例:

// Node.js 监听 orders 集合变更
const pipeline = [
  { $match: { "fullDocument.status": "active" } },  // 仅关注 active 订单
  { $project: { operationType: 1, fullDocument: 1, ns: 1, documentKey: 1 } }
]

const changeStream = db.collection("orders").watch(pipeline, {
  fullDocument: "updateLookup",            // update 时返回完整文档
  resumeAfter: { _data: "..." },            // 断点续传起点
  maxAwaitTimeMS: 1000,                    // 无变更时等待时间
  batchSize: 100
})

changeStream.on("change", (change) => {
  console.log("变更事件:", change.operationType)
  // insert / update / replace / delete / drop / rename / invalidate
  
  switch (change.operationType) {
    case "insert":
      handleNewOrder(change.fullDocument)
      break
    case "update":
      handleOrderUpdate(change.documentKey, change.fullDocument)
      break
    case "delete":
      handleOrderDelete(change.documentKey)
      break
  }
})

changeStream.on("error", (error) => {
  // 错误处理(可恢复 vs 不可恢复)
  if (error.code === 280) {  // ChangeStreamHistoryLost
    // Resume token 失效,需重新建立 watch
  }
})

// 关闭流
changeStream.close()

事件结构:

// insert 事件
{
  _id: { _data: "8265F8..." },          // resume token
  operationType: "insert",
  clusterTime: Timestamp(1700000000, 1),
  ns: { db: "mydb", coll: "orders" },
  documentKey: { _id: ObjectId("...") },
  fullDocument: { _id: ..., user_id: 10001, total: 99.9, status: "active" }
}

// update 事件(需 fullDocument: "updateLookup")
{
  _id: { _data: "..." },
  operationType: "update",
  clusterTime: Timestamp(...),
  ns: { db: "mydb", coll: "orders" },
  documentKey: { _id: ObjectId("...") },
  updateDescription: {
    updatedFields: { status: "shipped" },
    removedFields: [],
    truncatedArrays: []
  },
  fullDocument: { _id: ..., status: "shipped", ... }  // 更新后完整文档
}

// delete 事件
{
  _id: { _data: "..." },
  operationType: "delete",
  clusterTime: Timestamp(...),
  ns: { db: "mydb", coll: "orders" },
  documentKey: { _id: ObjectId("...") }
}

Change Stream 与 oplog 关系:

维度oplogChange Stream
访问层级内部(local.oplog.rs)公开 API(任何客户端可订阅)
权限要求需 direct access需 readAnyDatabase 等权限
数据格式二进制 BSON 内部格式友好 JSON 格式(包含 ns / operationType)
过滤能力无(需应用层过滤)支持 $match 管道(性能更优)
断点续传需手动记录 ts内建 resumeToken
分片集群仅 Primary 节点跨分片聚合全局事件
使用难度

Change Stream 高级用法:

// 1. 监听整个数据库
db.getSiblingDB("mydb").watch()

// 2. 监听整个集群
db.adminCommand({ aggregate: 1, pipeline: [...], cursor: {} })

// 3. 复杂过滤管道
const pipeline = [
  {
    $match: {
      $and: [
        { "ns.coll": { $in: ["orders", "payments"] } },
        {
          $or: [
            { operationType: "insert", "fullDocument.total": { $gt: 1000 } },
            { operationType: "update", "updateDescription.updatedFields.status": "refunded" },
            { operationType: "delete" }
          ]
        }
      ]
    }
  }
]

// 4. 断点续传(生产必备)
let resumeToken = null

const changeStream = db.orders.watch(pipeline, {
  resumeAfter: resumeToken               // 从上次中断点继续
})

changeStream.on("change", (change) => {
  resumeToken = change._id               // 保存 resume token
  // 处理变更
  processChange(change)
})

// 5. 配合 dead letter queue
changeStream.on("change", async (change) => {
  try {
    await processChange(change)
  } catch (error) {
    await deadLetterQueue.push({ change, error: error.message })
  }
})

Change Stream 在 Kafka 生态的应用:

# MongoDB Kafka Connector 配置示例
# 从 MongoDB 同步变更到 Kafka
name: mongodb-source-orders
config:
  connector.class: com.mongodb.kafka.connect.MongoSourceConnector
  connection.uri: mongodb://mongo-1:27017,mongo-2:27017,mongo-3:27017/?replicaSet=rs0
  database: mydb
  collection: orders
  # 复制集与 Change Stream 配置
  change.stream.full.document: updateLookup
  # 输出到 Kafka topic
  topic.prefix: mongodb.
  # Pipeline 过滤
  pipeline: |
    [
      { "$match": { "operationType": { "$in": ["insert", "update", "delete"] } } }
    ]
  # 起始位置
  startup.mode: resume

Change Stream 限制:

限制说明
仅支持复制集 / 分片集群单机模式不支持
oplog 窗口依赖oplog 太小可能导致 Change Stream 失效(resume token 失效)
权限要求find + local 数据库 read 权限
Capped CollectionCapped collection 删除事件可能不准确
Renaming / Dropping集合重命名 / 删除时需重新建立 watch
大事务延迟事务中的变更在提交后才可见(避免读取未提交数据)
10 GridFS 大文件存储

答案:

GridFS 是 MongoDB 内置的大文件存储规范,将大文件(默认超过 16MB 的单个 BSON 文档限制)拆分为多个 255KB 的 chunk 存储到两个集合中,提供透明的文件存取 API。

GridFS 存储结构:

graph TD
    File["原始文件
(如 100MB 视频)"] File --> Split["拆分为 255KB Chunk"] Split --> C1["Chunk 1
(0-255KB)"] Split --> C2["Chunk 2
(255-510KB)"] Split --> C3["Chunk 3
(510KB+)"] Split --> CN["Chunk N"] C1 --> FilesColl["fs.files 集合
(文件元数据)"] C2 --> ChunksColl["fs.chunks 集合
(二进制块)"] C3 --> ChunksColl CN --> ChunksColl FilesColl --> Meta["{
_id: ObjectId,
filename: 'video.mp4',
length: 104857600,
chunkSize: 261120,
uploadDate: ISODate,
md5: '...',
metadata: {}
}"] ChunksColl --> ChunkData["{
_id: ObjectId,
files_id: ObjectId('...'),
n: 0, // 块序号
data: BinData(...) // 二进制
}"]

GridFS 使用示例:

// Node.js GridFS Bucket API(推荐)
const { MongoClient, GridFSBucket } = require("mongodb")
const client = new MongoClient("mongodb://localhost:27017")

// 打开 GridFS Bucket
const db = client.db("mydb")
const bucket = new GridFSBucket(db, {
  bucketName: "fs",                      // 集合前缀(fs.files / fs.chunks)
  chunkSizeBytes: 261120                 // 255KB(默认)
})

// 1. 上传文件
const fs = require("fs")
const uploadStream = bucket.openUploadStream("video.mp4", {
  metadata: { category: "tutorial", uploaded_by: "zhangsan" }
})

fs.createReadStream("./local-video.mp4")
  .pipe(uploadStream)
  .on("error", (err) => console.error("上传失败:", err))
  .on("finish", () => {
    console.log("上传完成, fileId:", uploadStream.id)
  })

// 2. 下载文件
const downloadStream = bucket.openDownloadStreamByName("video.mp4")
downloadStream.pipe(fs.createWriteStream("./downloaded.mp4"))

// 3. 通过 _id 下载
const fileId = ObjectId("...")
bucket.openDownloadStream(fileId)
  .pipe(fs.createWriteStream("./by-id.mp4"))

// 4. 列出文件
const cursor = bucket.find({ "metadata.category": "tutorial" })
cursor.forEach(file => {
  console.log(`${file.filename} - ${file.length} bytes`)
})

// 5. 删除文件
bucket.delete(fileId)

// 6. 范围下载(部分内容)
bucket.openDownloadStreamByName("video.mp4", {
  start: 1024,                           // 起始字节
  end: 2048                              // 结束字节
})

GridFS 集合结构:

// fs.files 集合 - 文件元数据
{
  _id: ObjectId("..."),
  filename: "presentation.pptx",
  length: 5242880,                        // 文件总大小
  chunkSize: 261120,                      // chunk 大小
  uploadDate: ISODate("2024-01-15T10:00:00Z"),
  md5: "5d41402abc4b2a76b9719d911017c592",
  contentType: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
  metadata: {
    uploaded_by: "zhangsan",
    department: "engineering",
    tags: ["internal", "training"]
  },
  aliases: ["deck.pptx"]
}

// fs.chunks 集合 - 二进制块
{
  _id: ObjectId("..."),
  files_id: ObjectId("..."),             // 关联到 fs.files._id
  n: 0,                                  // chunk 序号(0 起始)
  data: BinData(0, "...")                 // 255KB 二进制数据
}

GridFS 配置选项:

// 自定义 GridFS Bucket
const bucket = new GridFSBucket(db, {
  bucketName: "attachments",              // 自定义前缀
  chunkSizeBytes: 1024 * 1024,            // 1MB(适合大视频)
  writeConcern: { w: "majority" },        // 多数派写入
  readPreference: "primaryPreferred"      // 读偏好
})

GridFS vs 对象存储对比:

维度GridFSS3 / OSS / COS
部署复杂度复用 MongoDB 集群需独立对象存储服务
单文件大小理论无上限(实际受分片集群限制)单文件 5TB
成本数据库存储(昂贵)对象存储(便宜)
数据一致性强一致(MongoDB 事务保障)最终一致
访问延迟较低(同集群)跨网络(高)
适用场景小型文件(< 1GB)、需事务大文件、CDN 加速
流式读写支持支持
索引能力强(自定义 metadata 索引)弱(仅按 key)
跨区域复制通过 MongoDB 复制集原生支持

GridFS 适用场景:

场景适用性
用户头像、缩略图(< 1MB)适合
小型文档附件(PDF/Excel < 10MB)适合
中等视频(10MB ~ 1GB)评估(建议用对象存储)
大型视频 / 备份(> 1GB)不推荐(用对象存储 + CDN)
需强一致与事务适合(GridFS 文档可纳入事务)
高频读 CDN 场景不推荐(用对象存储)

GridFS 性能优化:

优化点方法
Chunk Size 调优大文件用 1MB~2MB chunk,减少 fs.chunks 文档数
Index 优化{ files_id: 1, n: 1 } 是默认索引,附加 metadata 字段索引
分离 Bucket冷热数据用不同 Bucket,便于归档
数据生命周期通过 TTL 索引 + 后台任务定期清理
CDN 配合将 GridFS 文件转存到 CDN,降低 MongoDB 压力
// 优化索引
db.fs.chunks.createIndex({ files_id: 1, n: 1 })            // 默认索引
db.fs.files.createIndex({ "metadata.department": 1 })      // 业务查询索引
db.fs.files.createIndex({ uploadDate: -1 })                // 按时间清理

// 定期清理过期文件
db.fs.files.aggregate([
  { $match: { uploadDate: { $lt: new Date(Date.now() - 90 * 86400000) } } },
  { $group: { _id: null, file_ids: { $push: "$_id" } } }
]).forEach(result => {
  result.file_ids.forEach(id => {
    bucket.delete(id)
  })
})
11 备份恢复策略:mongodump / mongorestore / oplog

答案:

MongoDB 备份恢复体系由逻辑备份(mongodump / mongorestore)、物理备份(文件系统快照)、oplog 增量备份(mongooplog)三种方式组成,分别适用于不同场景与 RPO(Recovery Point Objective)要求。

备份方式对比:

备份方式原理RPO恢复粒度影响适用场景
mongodump集合级 BSON 导出备份时刻数据集合 / 文档级中(读压力)中小数据量、跨版本迁移
文件系统快照LVM/ZFS/Snapshot备份时刻数据整库大数据量、定期全量
mongooplog备份后 oplog 增量秒级整库极低灾难恢复、误操作回滚
MongoDB Atlas 备份云服务托管分钟级整库 / 时间点无自管云上托管
Sharded Cluster 备份协调 mongodump备份时刻数据库分片集群

mongodump 逻辑备份:

# 全库备份
mongodump --host "rs0/mongo-1:27017,mongo-2:27017" \
          --port 27017 \
          --username backup_user \
          --password "${MONGO_PASSWORD}" \
          --authenticationDatabase admin \
          --gzip \                            # 启用压缩
          --archive=mongo-backup-20240115.gz  # 单文件归档

# 单数据库备份
mongodump --host rs0/mongo-1:27017 \
          --db mydb \
          --gzip \
          --archive=mydb-backup-20240115.gz

# 单集合备份
mongodump --host rs0/mongo-1:27017 \
          --db mydb \
          --collection orders \
          --gzip \
          --archive=orders-backup-20240115.gz

# 指定字段查询备份(部分数据)
mongodump --host rs0/mongo-1:27017 \
          --db mydb \
          --collection orders \
          --query '{"created_at": {"$gte": {"$date": "2024-01-01T00:00:00Z"}}}' \
          --gzip \
          --archive=orders-2024.gz

# 并行备份(加速)
mongodump --host rs0/mongo-1:27017 \
          --numParallelCollections 4 \      # 4 集合并行
          --gzip \
          --archive=mongo-backup-20240115.gz

mongorestore 恢复:

# 恢复全库
mongorestore --host rs0/mongo-1:27017 \
             --username restore_user \
             --password "${MONGO_PASSWORD}" \
             --authenticationDatabase admin \
             --gzip \
             --archive=mongo-backup-20240115.gz \
             --drop                            # 恢复前删除已存在集合

# 恢复到不同数据库
mongorestore --host rs0/mongo-1:27017 \
             --nsInclude "mydb.*" \
             --nsFrom "mydb.*" --nsTo "mydb_restored.*" \
             --gzip \
             --archive=mongo-backup-20240115.gz

# 单集合恢复
mongorestore --host rs0/mongo-1:27017 \
             --db mydb \
             --collection orders \
             --gzip \
             --archive=orders-backup-20240115.gz

# 并行恢复
mongorestore --host rs0/mongo-1:27017 \
             --numParallelCollections 4 \
             --numInsertionWorkersPerCollection 8 \
             --gzip \
             --archive=mongo-backup-20240115.gz

# 索引重建(恢复时禁用索引构建,最后批量构建)
mongorestore --host rs0/mongo-1:27017 \
             --noIndexRestore \
             --gzip \
             --archive=mongo-backup-20240115.gz
# 恢复后单独构建索引
mongosh "mongodb://rs0/mongo-1:27017" --eval "
  db.orders.createIndex({ user_id: 1, created_at: -1 })
"

oplog 增量备份与 Point-In-Time Recovery:

# 1. 开启 oplog 监听(持续备份 oplog 到文件)
mongooplog --host rs0/mongo-1:27017 \
           --from rs0/mongo-1:27017 \
           --seconds 86400 \                # 监听 24 小时
           > /backup/oplog-20240115.bson

# 2. 备份后应用 oplog(Point-In-Time Recovery)
# 假设 14:00 备份后,14:30 发生误操作,需要恢复到 14:25 状态

# 步骤 1:恢复 14:00 全量备份
mongorestore --host rs0/mongo-1:27017 \
             --drop \
             --archive=mongo-backup-1400.gz

# 步骤 2:应用 oplog 到 14:25
mongorestore --host rs0/mongo-1:27017 \
             --oplogReplay \
             --oplogLimit "1705305900:1"    # oplog 时间戳(14:25 对应位置)

PITR(Point-In-Time Recovery)实战:

// 1. 查找目标时间点的 oplog 位置
// 查询 oplog,找到 14:25:00 之前的最后一个操作
use local
db.oplog.rs.find({
  ts: { $lte: Timestamp(1705305900, 1) }    // 14:25:00 的 Timestamp
}).sort({ ts: -1 }).limit(1)

// 2. 记录该 ts,作为 --oplogLimit 参数
// 3. 执行恢复(如上)

分片集群备份策略:

# 分片集群的备份需要协调每个 Shard + Config Server

# 1. 停止 Balancer(避免备份期间 Chunk 迁移)
mongosh "mongodb://mongos-1:27017" --eval "sh.stopBalancer()"

# 2. 备份 Config Server
mongodump --host config-1:27017 \
          --db config \
          --gzip \
          --archive=config-backup.gz

# 3. 备份每个 Shard(可并行)
mongodump --host shard-1-primary:27017 \
          --gzip \
          --archive=shard1-backup.gz &
mongodump --host shard-2-primary:27017 \
          --gzip \
          --archive=shard2-backup.gz &
mongodump --host shard-3-primary:27017 \
          --gzip \
          --archive=shard3-backup.gz &
wait

# 4. 恢复后重启 Balancer
mongosh "mongodb://mongos-1:27017" --eval "sh.startBalancer()"

备份策略金字塔:

graph TD
    L1["异地灾备
跨区域复制 S3 / OSS
每周 1 次 / 保留 3 个月"] L2["物理快照
LVM/ZFS/CSI Snapshot
每天 1 次 / 保留 7 天"] L3["逻辑全量 + oplog
mongodump + mongooplog
PITR 恢复
每天全量 + 持续 oplog / 保留 30 天"] L1 --> L2 --> L3

备份验证 SOP:

步骤频率操作
备份完整性校验每次备份后校验归档文件 MD5
自动恢复测试每周备份恢复至独立测试环境,验证数据一致性
PITR 演练每月模拟误操作,执行 PITR 恢复
恢复时间记录每次演练记录 RTO(恢复时间目标)
备份监控告警实时备份失败立即告警

生产备份规范:

# Kubernetes CronJob 备份示例
apiVersion: batch/v1
kind: CronJob
metadata:
  name: mongodb-backup
spec:
  schedule: "0 2 * * *"                  # 每天凌晨 2 点
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: mongo:7.0
            command:
            - /bin/sh
            - -c
            - |
              mongodump --host mongo-primary:27017 \
                        --username $BACKUP_USER \
                        --password $BACKUP_PASSWORD \
                        --authenticationDatabase admin \
                        --gzip \
                        --archive=/backup/mongo-$(date +%Y%m%d).gz
              
              # 上传到 S3
              aws s3 cp /backup/mongo-$(date +%Y%m%d).gz \
                        s3://mongo-backups/daily/
              
              # 清理 30 天前本地备份
              find /backup -name "*.gz" -mtime +30 -delete
            env:
            - name: BACKUP_USER
              valueFrom:
                secretKeyRef:
                  name: mongo-credentials
                  key: username
            - name: BACKUP_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mongo-credentials
                  key: password
          restartPolicy: OnFailure
12 MongoDB 性能调优与慢查询分析

答案:

MongoDB 性能调优是一个系统性工程,需从 Schema 设计、索引策略、查询模式、WiredTiger 配置、硬件资源五个维度综合优化,核心目标是降低查询延迟与提升并发吞吐。

性能调优方法论:

graph TD
    Start["发现性能问题"]
    Start --> Profile1["1. 慢查询识别
profiler / slow query log"] Profile1 --> Analyze["2. explain() 分析执行计划"] Analyze --> Identify["3. 定位瓶颈
索引缺失/锁等待/网络"] Identify --> Optimize["4. 优化
索引/Schema/查询/配置"] Optimize --> Verify["5. 验证效果"] Verify --> Profile1 Verify -->|未达预期| Profile1 Verify -->|达标| End["完成优化"]

1. 慢查询识别与 Profiler:

// 启用 Profiler(记录慢查询)
db.setProfilingLevel(2)                   // 记录所有操作
db.setProfilingLevel(1, { slowms: 100 })  // 记录 > 100ms 的操作(推荐)

// 查看 Profiler 状态
db.getProfilingStatus()

// 分析慢查询
db.system.profile.find({
  millis: { $gt: 100 }                    // 慢查询阈值
}).sort({ ts: -1 }).limit(20).forEach(doc => {
  print(JSON.stringify({
    ts: doc.ts,
    op: doc.op,
    ns: doc.ns,
    millis: doc.millis,
    query: doc.command?.filter || doc.command?.query,
    planSummary: doc.planSummary,
    numYields: doc.numYield,
    docsExamined: doc.docsExamined,
    keysExamined: doc.keysExamined,
    nreturned: doc.nreturned,
    responseLength: doc.responseLength
  }, null, 2))
})

// 慢查询关键指标
// docsExamined / nreturned  → 索引效率(理想 = 1)
// keysExamined / nreturned  → 索引选择性
// numYields                → 锁等待次数
// millis                   → 总耗时

// 关闭 Profiler
db.setProfilingLevel(0)

2. 慢查询优化手段:

// 反例 1:全表扫描(COLLSCAN)
db.orders.find({ status: "active", created_at: { $gte: ISODate("2024-01-01") } })
// 优化:创建复合索引
db.orders.createIndex({ status: 1, created_at: -1 })
// ESR 原则:等值 (status) → 范围/排序 (created_at)

// 反例 2:未使用覆盖索引
db.users.find({ email: "[email protected]" }, { username: 1, email: 1, _id: 0 })
// 优化:覆盖索引
db.users.createIndex({ email: 1, username: 1 })
// 查询时只读取索引,无 FETCH 阶段

// 反例 3:正则表达式前缀模糊查询
db.users.find({ name: /zhang/i })         // 无法使用索引
db.users.find({ name: /^zhang/ })        // 前缀匹配,可使用索引
// 前缀 /^value/ 命中索引,包含 /value/ 不命中

// 反例 4:$ne / $nin 操作符(索引效率低)
db.orders.find({ status: { $ne: "deleted" } })  // 索引扫描大部分
// 优化:使用 $in 替代
db.orders.find({ status: { $in: ["active", "pending", "shipped"] } })

// 反例 5:$exists 检查
db.users.find({ phone: { $exists: true } })  // 索引效率低
// 优化:使用 $type 替代
db.users.find({ phone: { $type: "string" } })

3. Schema 设计优化:

// 反例:宽表 + 大量字段
{
  _id: ObjectId("..."),
  order_id: "O20240115-001",
  user_basic: { name: "zhangsan", age: 28 },  // 用户基本信息
  user_profile: { ... },                        // 扩展信息
  items: [...],                                 // 订单商品
  shipping: { ... },                            // 物流信息
  payment: { ... },                             // 支付信息
  ...
}
// 文档过大(接近 16MB 上限),更新频繁导致局部更新失效

// 优化:反范式化拆分(按读写模式)
// 集合 1:orders(订单核心信息)
{
  _id: ObjectId("..."),
  order_id: "O20240115-001",
  user_id: 10001,
  status: "active",
  total: 99.9,
  created_at: ISODate("2024-01-15")
}

// 集合 2:order_items(订单商品)
{
  _id: ObjectId("..."),
  order_id: "O20240115-001",
  product_id: 20001,
  qty: 2,
  price: 49.95
}

// 集合 3:order_shipping(物流信息)
{
  _id: ObjectId("..."),
  order_id: "O20240115-001",
  carrier: "SF",
  tracking_no: "SF1234567890"
}

4. WiredTiger 调优:

# mongod.conf 性能调优
storage:
  wiredTiger:
    engineConfig:
      cacheSizeGB: 12                    # 缓存 = (RAM - OS reserve) × 50%
      journalCompressor: snappy
    
    collectionConfig:
      blockCompressor: snappy            # 集合压缩
    
    indexConfig:
      prefixCompression: true            # 索引前缀压缩

# 系统层面
net:
  maxIncomingConnections: 2000          # 最大连接数

# 操作 Profiling
operationProfiling:
  mode: slowOp
  slowOpThresholdMs: 100                # 慢查询阈值

# 资源限制
processManagement:
  fork: true
  timeZoneInfo: /usr/share/zoneinfo

5. 硬件与操作系统调优:

调优点建议
磁盘 I/ONVMe SSD,IOPS > 10K;WAL / 数据库分离
内存热数据 < WiredTiger cacheSize(推荐工作集占 60-70% 缓存)
CPU多核高频,WiredTiger 充分利用多核(文档级并发)
网络复制集节点间低延迟(< 5ms),避免跨 AZ 同步复制
NUMA 架构关闭 NUMA 或 numactl –interleave=all,避免跨 NUMA 内存访问
文件系统XFS(推荐)/ EXT4,禁用 atime,挂载选项 noatime
内核参数vm.swappiness=1(避免 swap),vm.dirty_ratio=15

6. 性能监控关键指标:

// 1. 实时 QPS / 延迟
db.serverStatus().opcounters
// - insert / query / update / delete / command / getmore

// 2. 连接数
db.serverStatus().connections
// - current / available / totalCreated / active

// 3. 复制状态
db.serverStatus().repl
// - replSetName / ismaster / secondary / primary

// 4. WiredTiger 缓存
db.serverStatus().wiredTiger.cache
// - "bytes currently in the cache" / "maximum bytes configured"
// - "tracked dirty bytes in the cache"
// - "pages read into cache" / "pages written from cache"
// - "percentage overhead"

// 5. 内存使用
db.serverStatus().mem
// - resident / virtual / mapped

// 6. 锁统计
db.serverStatus().locks
// - globalLock.totalTime / currentQueue

7. 慢查询优化清单:

优化点检查方法
索引覆盖查询explain().executionStats.totalDocsExamined == nReturned
避免 COLLSCANexplain().winningPlan.stage != "COLLSCAN"
避免内存排序explain().winningPlan.inputStage.stage != "SORT"
避免 Fetch 阶段使用 projection 限制返回字段
避免大 Skip使用 cursor-based 分页替代 skip(N)
避免大文档单文档 < 200KB,避免文档爆炸
避免深度嵌套嵌套层级 < 5 层,便于索引与查询
13 MongoDB 运维监控与诊断

答案:

MongoDB 运维监控体系包括内建命令、serverStatus 指标、Profiling 日志、Prometheus exporter 集成,需从运行状态、性能、复制、磁盘、连接、慢查询六个维度建立完整监控。

监控体系架构:

graph TD
    subgraph Mongo["MongoDB Cluster"]
        M1["mongod Primary"]
        M2["mongod Secondary"]
        M3["Config Server"]
    end
    
    subgraph Exporters["Metrics Exporters"]
        MExp["mongodb_exporter
(Prometheus 格式)"] BE["Bikeshed / mtools
(日志分析)"] end subgraph Stack["监控告警栈"] Prom["Prometheus"] Graf["Grafana"] Alert["AlertManager"] end M1 -->|"暴露 /metrics"| MExp M2 -->|"暴露 /metrics"| MExp M3 -->|"暴露 /metrics"| MExp M1 -->|"输出日志"| BE MExp --> Prom BE --> Prom Prom --> Graf Prom --> Alert Alert -->|告警| OnCall["OnCall 通知"]

1. 内建监控命令:

// serverStatus:综合运行状态
db.serverStatus()
// 关键字段:
// - host / process / pid / uptime / version
// - storageEngine: { name: "wiredTiger" }
// - connections: { current, available, totalCreated, active }
// - mem: { resident, virtual, mapped }
// - opcounters: { insert, query, update, delete, command, getmore }
// - opLatencies: { reads, writes, commands }
// - repl: { replSetName, ismaster, secondary, primary }
// - wiredTiger.cache: { ... }
// - locks: { ... }

// dbStats:集合 / 数据库统计
db.stats()
// 关键字段:
// - collections / objects / avgObjSize / dataSize / storageSize
// - indexes / indexSize
// - fsUsedSize / fsTotalSize

// collStats:单集合详细统计
db.orders.stats(1024)  // 1024 字节缩放
// 关键字段:
// - size / count / avgObjSize / maxSize
// - storageSize / totalIndexSize
// - wiredTiger: { ... } 详细存储信息

// currentOp:当前操作
db.currentOp({
  $or: [
    { "op": { $in: ["query", "update", "insert", "remove"] } },
    { "command.findAndModify": { $exists: true } }
  ],
  "secs_running": { $gt: 5 }             // 超过 5 秒
})

// 终止慢查询
db.killOp(opid)

// top:按集合统计访问量
db.adminCommand({ top: 1 })

// hostInfo:服务器信息
db.hostInfo()
// - system / os / extra

2. 复制集监控:

// 复制集状态
rs.status()
// 关键字段:
// - set: 集群名
// - myState: 1 (PRIMARY) / 2 (SECONDARY)
// - members[]:
//   - stateStr
//   - health
//   - optimeDate: 最后应用 oplog 时间
//   - electionDate
//   - syncSourceHost

// 主从延迟
rs.printSecondaryReplicationInfo()
// 输出:source: mongo-2, syncedTo: ..., lag: 2s

// oplog window:oplog 可回放时长
db.adminCommand({ replSetGetStatus: 1 }).members.map(m => ({
  name: m.name,
  state: m.stateStr,
  lag_seconds: (Date.now() - m.optimeDate?.getTime()) / 1000
}))

3. Prometheus 集成:

# mongodb_exporter 部署
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mongodb-exporter
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mongodb-exporter
  template:
    metadata:
      labels:
        app: mongodb-exporter
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9216"
        prometheus.io/path: "/metrics"
    spec:
      containers:
      - name: exporter
        image: percona/mongodb_exporter:0.40
        args:
        - "--mongodb.uri=mongodb://mongo-exporter:${PASSWORD}@mongo-primary:27017/admin"
        - "--collect-all"
        - "--compatible-mode"
        ports:
        - containerPort: 9216
        env:
        - name: PASSWORD
          valueFrom:
            secretKeyRef:
              name: mongodb-credentials
              key: password

关键 Prometheus 指标:

指标说明告警阈值
mongodb_ss_connections{conn_type="current"}当前连接数> 80% maxIncomingConnections
mongodb_ss_opcounters_total操作计数器(rate 计算 QPS)
mongodb_ss_mem_resident_bytes物理内存使用> 80% node memory
mongodb_ss_wt_cache_bytes_currently_in_cacheWiredTiger 缓存使用> 90% cacheSize
mongodb_ss_wt_cache_tracked_dirty_bytes脏页大小> 20% cacheSize
mongodb_ss_repl_secondary_delay_seconds复制延迟> 30s
mongodb_ss_repl_health节点健康状态!= 1
mongodb_ss_opLatencies_reads_op_time读操作延迟> 100ms
mongodb_ss_opLatencies_writes_op_time写操作延迟> 100ms
mongodb_ss_storageEngine_dataSize_bytes数据大小持续增长监控

PromQL 告警规则:

# Prometheus 告警规则示例
groups:
- name: mongodb_alerts
  rules:
  # 复制集 Primary 不可用
  - alert: MongoDBPrimaryDown
    expr: mongodb_rs_members_state == 1
    for: 30s
    labels:
      severity: critical
    annotations:
      summary: "MongoDB Primary 不可用"

  # 复制延迟
  - alert: MongoDBReplicationLag
    expr: mongodb_rs_members_optimeDate - mongodb_rs_members_primary_optimeDate > 30
    for: 2m
    labels:
      severity: warning
    annotations:
      summary: "MongoDB 复制延迟 > 30s"

  # WiredTiger 缓存使用率高
  - alert: MongoDBHighCacheUsage
    expr: mongodb_ss_wt_cache_bytes_currently_in_cache / mongodb_ss_wt_cache_maximum_bytes_configured > 0.9
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "WiredTiger 缓存使用率 > 90%"

  # 脏页率过高
  - alert: MongoDBHighDirtyPages
    expr: mongodb_ss_wt_cache_tracked_dirty_bytes / mongodb_ss_wt_cache_maximum_bytes_configured > 0.2
    for: 10m
    labels:
      severity: warning
    annotations:
      summary: "WiredTiger 脏页率 > 20%"

  # 连接数高
  - alert: MongoDBHighConnections
    expr: mongodb_ss_connections{conn_type="current"} > 1600
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "MongoDB 连接数 > 1600"

  # 备份缺失
  - alert: MongoDBNoRecentBackup
    expr: time() - mongodb_backup_last_success_time > 86400
    for: 1h
    labels:
      severity: critical
    annotations:
      summary: "MongoDB 24 小时内无成功备份"

4. 慢查询日志分析:

# mtools:MongoDB 日志分析工具集
pip install mtools

# 常用 mloginfo / mlogfilter / mplotqueries
mloginfo mongo.log                          # 整体统计
mlogfilter mongo.log --slow 100             # 提取 > 100ms 慢查询
mlogfilter mongo.log --namespace mydb.orders # 提取特定集合
mplotqueries mongo.log --type scatter       # 慢查询可视化

5. 故障排查 Checklist:

故障现象排查步骤
查询慢1. 开启 Profiler 2. explain() 分析 3. 检查索引 4. 检查 WiredTiger 缓存
写入慢1. 检查 oplog 写入延迟 2. 检查磁盘 I/O (iostat) 3. 检查 writeConcern 4. 检查索引数量
Primary 切换频繁1. 检查节点健康 2. 检查网络分区 3. 检查选举参数 4. 检查资源 (CPU/Mem/Disk)
复制延迟大1. 检查 Secondary 资源 2. 检查网络 3. 检查 oplog 写入 4. 检查 Secondary 锁竞争
磁盘占满1. 检查 oplog 大小 2. 检查索引膨胀 3. 检查文档删除后的空间回收 4. 考虑 compact / repair
连接数耗尽1. 检查应用连接池 2. 检查慢查询占用连接 3. 调整 maxIncomingConnections

6. 性能基线与容量规划:

MongoDB 性能基线计算公式:

内存需求:
  cacheSizeGB = (热数据 + 索引) × 1.3(30% 余量)
  推荐:cacheSizeGB ≤ RAM × 50% - 1GB
  系统内存建议:cacheSizeGB × 2.5

磁盘需求:
  数据大小:原始 JSON × 1.5(BSON 开销) × 1.3(压缩后) × 1.2(索引开销)
  加上 oplog(默认 5% 磁盘)
  加上 WiredTiger checkpoint 临时空间(≈ 数据量 2%)

CPU 需求:
  OLTP:每 1000 QPS 需约 2 vCPU
  复杂聚合:每 100 QPS 需约 4 vCPU

网络需求:
  复制流量:oplog 写入速率 × 副本数
  客户端流量:峰值 QPS × 平均响应体大小
14 MongoDB vs MySQL 对比与场景选型

答案:

MongoDB 与 MySQL 是当前最主流的两种数据库引擎,分别代表文档数据库与关系型数据库的典型范式。理解两者差异是技术选型的基础。

核心对比矩阵:

维度MongoDBMySQL
数据模型文档(BSON,动态 Schema)关系表(固定 Schema)
扩展方式水平扩展(Sharding 原生)垂直扩展 / 分库分表(应用层)
事务能力4.0+ 多文档 ACID,4.2+ 分布式事务完整 ACID 事务
JOIN 支持弱($lookup 4.0+ 起支持)强(多表 JOIN 优化成熟)
索引B+Tree、TTL、地理、文本、哈希B+Tree、HASH、FULLTEXT、SPATIAL
查询语言MongoDB Query Language (JSON-like)SQL
一致性模型默认最终一致(可配 w:majority 强一致)默认 ACID 强一致
写入性能高(无 JOIN,文档级锁)中(行级锁,InnoDB 复杂事务)
存储引擎WiredTigerInnoDB(默认)/ MyISAM / Memory
适用数据规模TB ~ PB 级GB ~ TB 级(分库分表后 PB 级)
运维复杂度中(复制集 + Sharding)中(主从 / MGR / 分库分表)
生态成熟度中(NoSQL 主流)极高(关系型事实标准)
学习曲线平缓(JSON-like)中(SQL 标准化)

数据结构对比:

-- MySQL:用户表(关系型)
CREATE TABLE users (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50) UNIQUE NOT NULL,
  email VARCHAR(100) NOT NULL,
  age INT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_email (email),
  INDEX idx_created (created_at)
);

CREATE TABLE user_profiles (
  user_id BIGINT PRIMARY KEY,
  real_name VARCHAR(50),
  address JSON,                          -- 5.7+ 支持 JSON 类型
  FOREIGN KEY (user_id) REFERENCES users(id)
);
// MongoDB:用户文档(文档型)
db.users.insertOne({
  username: "zhangsan",
  email: "[email protected]",
  age: 28,
  created_at: new Date(),
  profile: {                              // 嵌入文档
    real_name: "张三",
    address: {
      city: "Beijing",
      district: "Haidian"
    }
  },
  tags: ["vip", "tech"]                   // 数组
})

// 索引
db.users.createIndex({ email: 1 }, { unique: true })
db.users.createIndex({ created_at: -1 })

事务能力对比:

-- MySQL:转账事务(标准 ACID)
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 10001;
UPDATE accounts SET balance = balance + 100 WHERE user_id = 10002;
INSERT INTO transfers (from_user, to_user, amount) VALUES (10001, 10002, 100);
COMMIT;
// MongoDB:转账事务(4.0+)
const session = db.getMongo().startSession()
session.startTransaction()
try {
  const users = session.getDatabase("mydb").users
  const transfers = session.getDatabase("mydb").transfers
  
  users.updateOne({ _id: 10001 }, { $inc: { balance: -100 } }, { session })
  users.updateOne({ _id: 10002 }, { $inc: { balance: 100 } }, { session })
  transfers.insertOne({ from: 10001, to: 10002, amount: 100 }, { session })
  
  session.commitTransaction()
} catch (e) {
  session.abortTransaction()
} finally {
  session.endSession()
}

JOIN 查询对比:

-- MySQL:JOIN 查询
SELECT u.id, u.username, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at >= '2024-01-01'
GROUP BY u.id, u.username
HAVING order_count > 0
ORDER BY order_count DESC
LIMIT 10;
// MongoDB:$lookup 实现(性能通常弱于 MySQL JOIN)
db.users.aggregate([
  { $match: { created_at: { $gte: ISODate("2024-01-01") } } },
  {
    $lookup: {
      from: "orders",
      localField: "_id",
      foreignField: "user_id",
      as: "orders"
    }
  },
  {
    $project: {
      username: 1,
      order_count: { $size: "$orders" }
    }
  },
  { $match: { order_count: { $gt: 0 } } },
  { $sort: { order_count: -1 } },
  { $limit: 10 }
])

分库分表 vs Sharding 对比:

维度MySQL 分库分表(应用层)MongoDB Sharding(原生)
分片策略应用代码中实现数据库自动管理
数据迁移业务停机 + DBA 手工Balancer 自动迁移
路由逻辑中间件(ShardingSphere)或应用mongos 自动路由
跨分片查询复杂(中间件)支持(性能有损失)
跨分片事务复杂(TCC / Saga)4.2+ 支持(但性能开销大)
运维成本

典型场景选型:

场景推荐原因
金融核心账务MySQL强 ACID、严格一致性、复杂 JOIN、报表查询
电商订单(写多)MongoDBSchema 灵活、海量数据、高写入吞吐
用户画像 / 标签MongoDB字段动态、嵌套结构、宽表读取
内容管理(CMS)MongoDB文章+评论+标签天然文档化
物联网时序数据MongoDB(5.0+ Time Series)高写入、TTL、聚合分析
BI / 数据仓库MySQL / ClickHouse复杂 SQL、聚合分析、JOIN
社交关系链MongoDB / Neo4j图结构、深度查询
缓存层MongoDB / Redis替代 Redis 持久化方案
传统 ERP / CRMMySQL业务成熟、报表多、复杂事务
跨数据中心MongoDB(Zone Sharding)原生支持地理分片

选型决策树:

是否需要强事务 + 复杂 SQL + 报表分析?
  ├── 是 → 优先 MySQL(或其他 RDBMS)
  └── 否 →
        数据是否结构化固定?
          ├── 是 → MySQL
          └── 否(Schema 多变)→
                写入量是否 > 1万 QPS?
                  ├── 是 → MongoDB(Sharding 水平扩展)
                  └── 否 →
                        是否需要灵活字段 / 嵌套结构?
                          ├── 是 → MongoDB
                          └── 否 → MySQL

共存架构:

生产环境常见组合:

MySQL  →  核心账务、交易、订单状态(强一致)
MongoDB →  商品详情、用户画像、日志、IoT 数据(半结构化)
Redis   →  缓存层、Session、计数器
ClickHouse →  OLAP 分析、日志分析
Elasticsearch →  全文搜索、日志检索

各司其职,通过数据同步工具(Debezium / DataX / Canal)打通

MongoDB 不适用的场景:

不适用场景原因
强事务 + 复杂 JOIN 报表关系模型更合适,MongoDB 聚合管道性能差
大量多对多关联查询MongoDB 引用模式查询性能随数据量下降
财务审计、监管严格传统 RDBMS 工具链更成熟
大量复杂事务(每秒数千次跨文档事务)MongoDB 事务性能开销大
团队 SQL 能力强、NoSQL 经验少切换成本与风险
15 MongoDB 生产案例与故障排查

答案:

本节通过典型生产案例展示 MongoDB 在容量、性能、高可用、故障恢复等方面的实战经验与排查方法。

案例 1:WiredTiger 缓存不足导致全表扫描

现象:

某电商平台订单服务偶发查询延迟飙升至 5s+,业务高峰期出现报警。

监控数据:
- WiredTiger 缓存使用率持续 > 95%
- slow query 数量激增
- 多个查询 explain 显示 COLLSCAN

排查过程:

// 1. 确认缓存状态
db.serverStatus().wiredTiger.cache
// {
//   "bytes currently in the cache": 13600000000,    // 12.6GB
//   "maximum bytes configured": 13800000000,        // 12.9GB
//   "tracked dirty bytes in the cache": 2700000000,
//   "pages read into cache": 15230000,                // 持续从磁盘读取
//   "percentage overhead": 7
// }

// 2. 定位慢查询
db.system.profile.find({ millis: { $gt: 1000 } }).sort({ ts: -1 }).limit(10)
// 发现:db.orders.find({ status: "active" }) 频繁出现

// 3. 检查索引
db.orders.getIndexes()
// 已有:{ user_id: 1, created_at: -1 }
// 缺少:{ status: 1 }

// 4. 验证:执行计划
db.orders.find({ status: "active" }).explain("executionStats")
// winningPlan.stage = "COLLSCAN"
// totalDocsExamined = 5000000, nReturned = 80000

根因:

1. 业务变更新增"按订单状态查询"功能,但未创建 status 索引
2. status 字段低基数(仅 5 个值),命中索引时选择度低
3. 但因 orders 集合热数据 > WiredTiger cacheSize(12.9GB)
4. 缓存淘汰导致索引频繁失效
5. 查询退化为 COLLSCAN,性能急剧下降

解决方案:

// 1. 创建复合索引(ESR 原则:等值 status + 排序 created_at)
db.orders.createIndex(
  { status: 1, created_at: -1 },
  { background: true }                   // 后台构建,不阻塞
)

// 2. 优化查询条件(添加时间范围)
db.orders.find({
  status: "active",
  created_at: { $gte: ISODate("2024-01-01") }
}).sort({ created_at: -1 }).limit(20)

// 3. 扩容 WiredTiger 缓存
storage:
  wiredTiger:
    engineConfig:
      cacheSizeGB: 16                    // 从 12GB 扩容至 16GB

预防措施:

  • 慢查询监控 + 自动告警(> 500ms 持续 1min)
  • 索引审核流程(上线前 DBA 审核)
  • Profiler 开启,记录慢查询
  • 定期分析 db.collection.aggregate([{$indexStats: {}}]) 索引使用率

案例 2:分片集群 Jumbo Chunk 导致数据倾斜

现象:

某分片集群 Shard-1 数据量达 3TB(其他 Shard 仅 1TB),磁盘使用率告警。
查询 Shard-1 上订单的延迟显著高于其他 Shard。

排查过程:

// 1. 查看 Chunk 分布
use config
db.chunks.aggregate([
  { $group: { _id: { shard: "$shard", ns: "$ns" }, count: { $sum: 1 }, total: { $sum: "$size" } } },
  { $match: { "total": { $gt: 100 } } },
  { $sort: { "total": -1 } }
])
// 发现:shard-1 上某 Chunk 异常大(jumbo flag: true)

// 2. 查看 Jumbo Chunk
db.chunks.find({ jumbo: true }).count()
// 100+ jumbo chunks

// 3. 查看 Shard Key
db.collections.find({ _id: "mydb.orders" })
// key: { region: 1, user_id: 1 }

根因:

1. 业务初期数据按 region 范围分片
2. 单一 region("us-east")数据量激增(占总量 70%+)
3. region 内 user_id 范围分片
4. 但 us-east 内 user_id 集中在某段(业务用户群特征)
5. Shard Key 选择不当,导致数据倾斜
6. 出现 Jumbo Chunk(无法继续拆分)

解决方案:

// 步骤 1:紧急手动拆分 Jumbo Chunk
use config

// 查找 us-east 大 Chunk
db.chunks.find({
  ns: "mydb.orders",
  "min.region": "us-east",
  jumbo: true
}).forEach(chunk => {
  // 强制拆分(注意:在低峰期执行)
  sh.splitAt("mydb.orders", chunk.max)
})

// 步骤 2:迁移拆分后的 Chunk
sh.startBalancer()

// 步骤 3:长期方案 - 调整 Shard Key
// 3.1 评估业务查询模式
// 3.2 业务侧实现应用层 Shard Key 写入新集群
// 3.3 双写双读切换
// 3.4 老集群下线

// 长期方案:使用 hashed 分片或更细粒度 Shard Key
// 例如:{ region: 1, user_id: "hashed" }  // region 范围 + user_id 哈希

预防措施:

  • 容量规划时考虑业务增长不均
  • 选择 Shard Key 时充分考虑数据倾斜风险
  • 监控 Chunk 分布与 Jumbo Chunk 数量
  • 定期评估 Shard Key 设计

案例 3:Oplog 窗口不足导致 Change Stream 失效

现象:

某团队使用 Change Stream 同步订单数据到 Kafka,出现"resume token 失效"错误。
Change Stream 自动断开,应用程序告警。

排查过程:

// 1. 查看 oplog 状态
rs.printReplicationInfo()
// oplog 起始与结束时间窗口

// 2. 计算 oplog window
use local
db.oplog.rs.stats()
// {
//   "maxSize": 51200000000,    // 50GB
//   "size": 51200000000,
//   "count": 15000000,
//   ...
// }

// 3. 查看 oplog window
db.adminCommand({ replSetGetStatus: 1 }).members.forEach(m => {
  print(m.name, m.optimeDate)
})
// 输出:mongo-1 2024-01-15 10:00:00  mongo-2 2024-01-15 09:55:00
// oplog window = 5min(过短)

// 4. 查看 oplog 写入速率
db.oplog.rs.aggregate([
  { $group: { _id: null, avg_size: { $avg: "$size" } } }
])

根因:

1. oplogSizeMB 默认是 5% 磁盘空间
2. 业务高写入速率(10万 ops/s)导致 oplog 快速填满
3. Secondary 节点追赶不及时,oplog window 缩短
4. Change Stream 消费速率跟不上 oplog 产生速率
5. resume token 对应的 oplog 已被覆盖 → Change Stream 失效

解决方案:

// 1. 紧急:调整 oplog 大小(需重启)
// mongod.conf
replication:
  oplogSizeMB: 102400                // 100GB(从 50GB 扩容)

// 重启 mongod(先 Secondary,再 Primary)
// 注意:oplogSizeMB 调整后需要重启生效

// 2. 优化 Change Stream 消费
// 2.1 增加消费者并发
changeStream.on("change", async (change) => {
  // 异步处理
  await processChangeAsync(change)
})

// 2.2 批量处理
let buffer = []
changeStream.on("change", (change) => {
  buffer.push(change)
  if (buffer.length >= 100) {
    processBatch(buffer)
    buffer = []
  }
})

// 3. 监控告警
// oplog window < 1h 触发告警
// Change Stream 断开立即告警

预防措施:

  • 监控 oplog window(replSetGetStatus
  • 监控 Change Stream lag
  • 设置合理的 oplogSizeMB
  • Change Stream 消费端实现断点续传 + 死信队列

案例 4:分片集群 Mongos 路由缓存不一致导致查询错误

现象:

业务反馈部分查询返回空数据,但实际数据存在。
多次重试后查询成功,问题偶发。

排查过程:

// 1. 检查 Balancer 状态
sh.status()
// 显示最近一次 Chunk 迁移

// 2. 查看 mongos 日志
// 出现 "could not find chunk" 错误

// 3. 查看 mongos 缓存
db.adminCommand({ getParameter: 1, userCacheInvalidationIntervalSecs: 1 })
// mongos 缓存更新默认 1s,业务端查询 < 1s 命中陈旧路由

根因:

1. Balancer 正在迁移 Chunk
2. mongos 缓存的路由信息未及时更新
3. 查询命中陈旧路由,定位到错误 Shard
4. 查询返回空或文档不存在

解决方案:

// 1. 临时 flush mongos 缓存(不重启)
db.adminCommand({ flushRouterConfig: 1 })

// 2. 优化 Balancer 时间窗口(业务低峰期迁移)
use config
db.settings.updateOne(
  { _id: "balancer" },
  { $set: { activeWindow: { start: "02:00", stop: "06:00" } } }
)

// 3. 应用层重试机制
async function findWithRetry(collection, filter, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await collection.find(filter).toArray()
    } catch (err) {
      if (err.codeName === "StaleConfig" && i < maxRetries - 1) {
        await sleep(100)                  // 短暂等待
        continue
      }
      throw err
    }
  }
}

// 4. 监控告警
// - StaleConfig 错误率
// - 路由缓存命中率
// - Balancer 迁移频率

预防措施:

  • 业务低峰期执行 Balancer
  • 应用层实现 StaleConfig 错误重试
  • 监控 Chunk 迁移频率与持续时间
  • 评估是否需要拆分大集合减少迁移频率

案例 5:副本集 Primary 频繁切换(Split-Brain 防护)

现象:

某 3 节点复制集在网络抖动时出现多次 Primary 切换,每次切换导致短暂写入失败。

排查过程:

// 1. 查看复制集状态历史
db.adminCommand({ replSetGetStatus: 1 }).members.forEach(m => {
  print(m.name, "electionDate:", m.electionDate, "state:", m.stateStr)
})

// 2. 查看 oplog 窗口
rs.printReplicationInfo()

// 3. 查看节点日志
// 出现 "Stepped down" / "Heartbeat failure" / "Election"

根因:

1. 节点分布在 3 个 AZ,AZ 间网络抖动
2. 节点间心跳延迟 > electionTimeoutMillis(10s)
3. 触发不必要的选举
4. 新 Primary 提升后,旧 Primary 网络恢复又重新加入
5. 多次切换导致服务抖动

解决方案:

// 1. 调整选举参数
cfg = rs.conf()
cfg.settings = {
  electionTimeoutMillis: 20000,         // 增加到 20s(容忍网络抖动)
  heartbeatIntervalMillis: 2000,
  catchUpTimeoutMillis: 30000
}
rs.reconfig(cfg)

// 2. 优化节点分布
// - Primary 与多数派节点在同 AZ
// - 少数派节点用于灾备(Priority 0)

cfg.members = [
  { _id: 0, host: "mongo-az-a-1:27017", priority: 2 },  // 主 AZ 高优先级
  { _id: 1, host: "mongo-az-a-2:27017", priority: 2 },
  { _id: 2, host: "mongo-az-b-1:27017", priority: 1 },  // 备 AZ
  { _id: 3, host: "mongo-az-c-1:27017", priority: 0, votes: 0 }  // DR
]
rs.reconfig(cfg)

// 3. 监控告警
// - Primary 切换次数(异常切换告警)
// - 心跳延迟
// - 选举耗时

预防措施:

  • 节点分布:主节点与多数派在同一 AZ
  • 调整 electionTimeoutMillis 容忍网络抖动
  • 监控选举次数与频率
  • 定期演练 Primary 切换(验证 Failover 流程)

生产运维 Checklist:

类别检查项频率
数据安全备份完整性、PITR 可恢复性每日
高可用Primary/Secondary 健康、心跳、选举实时
性能慢查询、缓存命中率、QPS/TPS实时
容量磁盘使用率、文档数量、索引大小每日
变更索引变更、Schema 变更、配置变更每次变更
安全鉴权、加密、审计日志、网络隔离每周
升级补丁升级、版本升级、兼容性测试按需
演练Failover、PITR 恢复、备份恢复每月