3.9 NoSQL 入门——MongoDB 文档数据库实战

预计阅读时间:16 分钟

📖 目录

3.6:MySQL 数据库 MySQL 和 3.7:PostgreSQL 数据库 PostgreSQL 覆盖了关系型数据库。MongoDB 是最流行的文档数据库(NoSQL),以灵活的 schema、水平扩展和 JSON 原生存储著称。本文介绍 MongoDB 的安装配置、CRUD 操作、数据建模和适用场景。

学习目标

  • 理解 NoSQL 与关系型数据库的本质差异——灵活 schema、无 JOIN、水平扩展
  • 完成 MongoDB 安装、服务管理与基本 CRUD 操作
  • 掌握索引的创建与验证,理解覆盖索引与复合索引的适用场景
  • 学会嵌入(Embedded)与引用(Reference)两种数据建模方式的取舍
  • 搭建副本集(Replica Set)实现高可用,并理解选举机制
  • 能根据业务场景判断"该用 MySQL 还是 MongoDB"

前置知识

  • 3.6:MySQL 数据库 MySQL/MariaDB 安装与管理——理解数据库的安装、服务、备份基本概念
  • 3.7:PostgreSQL 数据库 PostgreSQL 安装与管理——作为关系型数据库的对照基准
  • JSON 基本语法——MongoDB 文档即 JSON,操作前先熟悉其结构
  • 4.5:网络故障排查 网络故障排查——理解端口与服务监听概念(MongoDB 默认 27017)

1. 安装与配置

# Ubuntu 安装 MongoDB 7.0
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \
  sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor

echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] \
https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/7.0 multiverse" | \
  sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list

sudo apt update && sudo apt install -y mongodb-org mongodb-mongosh

# 启动并启用开机自启
sudo systemctl enable --now mongod

# 验证
mongod --version
mongosh  # MongoDB Shell(新版替代 mongo)

生产配置

# /etc/mongod.conf
storage:
  dbPath: /var/lib/mongodb
  journal:
    enabled: true
  wiredTiger:
    engineConfig:
      cacheSizeGB: 2  # WiredTiger 缓存,默认约为物理内存的 50%-1GB,生产按工作集调整

net:
  port: 27017
  bindIp: 0.0.0.0  # 生产环境限定内网 IP
  maxIncomingConnections: 100

security:
  authorization: enabled  # 启用认证

replication:
  replSetName: rs0  # 启用副本集

2. 基本操作(CRUD)

# 连接数据库
mongosh "mongodb://localhost:27017"

# 切换数据库(不存在则自动创建)
use myapp

# 创建用户
db.createUser({
  user: "appuser",
  pwd: "password123",
  roles: [{ role: "readWrite", db: "myapp" }]
})

插入文档

# 插入单条
db.users.insertOne({
  name: "张三",
  email: "zhangsan@example.com",
  age: 28,
  tags: ["admin", "developer"],
  address: { city: "北京", district: "海淀" },
  createdAt: new Date()
})

# 插入多条
db.users.insertMany([
  { name: "李四", email: "lisi@example.com", age: 32 },
  { name: "王五", email: "wangwu@example.com", age: 25 }
])

查询

# 精确查询
db.users.find({ "address.city": "北京" })

# 范围查询
db.users.find({ age: { $gte: 25, $lte: 35 } })

# 正则查询(name 包含"三")
db.users.find({ name: /三/ })

# 投影(只返回 name 和 email)
db.users.find({}, { name: 1, email: 1, _id: 0 })

# 排序 + 分页
db.users.find().sort({ age: -1 }).skip(0).limit(10)

# 聚合(按城市统计用户数)
db.users.aggregate([
  { $group: { _id: "$address.city", count: { $sum: 1 } } },
  { $sort: { count: -1 } }
])

更新

# 更新单条
db.users.updateOne(
  { name: "张三" },
  { $set: { age: 29 }, $push: { tags: "devops" } }
)

# 更新多条(所有 age>30 的用户加 vip 标签)
db.users.updateMany(
  { age: { $gt: 30 } },
  { $addToSet: { tags: "vip" } }
)

# 删除
db.users.deleteOne({ name: "王五" })
db.users.deleteMany({ age: { $lt: 18 } })

查询操作符全集

# 比较与逻辑操作符
db.users.find({ age: { $ne: 30 } })              # 不等于
db.users.find({ age: { $in: [25, 28, 32] } })    # 值在集合内
db.users.find({ age: { $nin: [25, 28] } })       # 值不在集合内
db.users.find({ $and: [{ age: { $gt: 20 } }, { tags: "admin" }] })
db.users.find({ $or: [{ "address.city": "北京" }, { "address.city": "上海" }] })
db.users.find({ $nor: [{ tags: "vip" }] })       # 所有条件都不满足

# 数组与存在性
db.users.find({ tags: "admin" })                 # 数组包含元素
db.users.find({ tags: { $size: 3 } })            # 数组长度为 3
db.users.find({ email: { $exists: true } })      # 字段存在
db.users.find({ email: { $exists: false } })     # 字段缺失
db.users.find({ nickname: { $type: "string" } }) # 按 BSON 类型过滤

# 计数与去重
db.users.countDocuments({ age: { $gt: 30 } })
db.users.distinct("address.city")
db.users.findOne({ email: "zhangsan@example.com" })   # 返回单条

更新操作符

# $inc 自增(原子操作,适合计数器)
db.users.updateOne(
  { name: "张三" },
  { $set: { age: 29 }, $inc: { loginCount: 1 } }
)

# $unset 删除字段 / $rename 重命名字段
db.users.updateOne({ name: "张三" }, { $unset: { nickname: "" } })
db.users.updateOne({ name: "张三" }, { $rename: { mobile: "phone" } })

# 数组操作:$pull 按值删除 / $pop 按位置删除
db.users.updateOne({ name: "张三" }, { $pull: { tags: "devops" } })
db.users.updateOne({ name: "张三" }, { $pop: { tags: -1 } })   # 删第一个元素

# upsert:存在则更新,不存在则插入(防并发抢单场景)
db.users.updateOne(
  { email: "test@example.com" },
  { $set: { name: "测试" } },
  { upsert: true }
)

# 批量操作:bulkWrite 混合多种写操作,一次网络往返
db.users.bulkWrite([
  { insertOne: { document: { name: "赵六", age: 40 } } },
  { updateOne: { filter: { name: "张三" }, update: { $set: { age: 30 } } } },
  { deleteOne: { filter: { name: "王五" } } }
])

事务:MongoDB 也有 ACID

MongoDB 4.0+ 支持多文档事务(副本集内),4.2+ 支持分片集群跨分片事务。但事务有代价——乐观并发控制 + 写冲突重试,吞吐低于单文档操作,只用于真正需要原子性的场景:

// 转账:扣款 + 入账两个文档必须同时成功
const session = db.getMongo().startSession()
session.startTransaction()
try {
  db.accounts.updateOne(
    { _id: "alice", balance: { $gte: 100 } },        // 条件检查防超扣
    { $inc: { balance: -100 } }, { session })
  db.accounts.updateOne(
    { _id: "bob" },
    { $inc: { balance: 100 } }, { session })
  session.commitTransaction()
} catch (e) {
  session.abortTransaction()
  print("转账失败:", e)
} finally {
  session.endSession()
}

// 事务适用清单:库存扣减 + 订单创建、余额变动 + 流水记录
// 不适用:日志写入、计数器、高并发短操作(单文档 $inc 就够了)

3. 聚合管道实战

聚合管道是 MongoDB 的"JOIN + GROUP BY":把文档按阶段(stage)流水线处理,每个阶段输出给下一个。以下用"订单分析"完整案例串联 $match / $lookup / $unwind / $group / $project / $sort:

// 场景:统计 2025 年 6 月每个城市的支付订单金额 Top 5
db.orders.aggregate([
  // 1. $match 尽早过滤,减少下游数据量(能用索引就用索引)
  { $match: {
      status: "paid",
      createdAt: { $gte: ISODate("2025-06-01"), $lt: ISODate("2025-07-01") }
  } },
  // 2. $lookup 关联 users 拿用户城市(相当于 LEFT JOIN)
  { $lookup: {
      from: "users",
      localField: "userId",
      foreignField: "_id",
      as: "user"
  } },
  // 3. $unwind 把 user 数组展开成单个对象
  { $unwind: "$user" },
  // 4. $group 按城市分组:sum 金额、count 订单数、avg 均价
  { $group: {
      _id: "$user.address.city",
      totalAmount: { $sum: "$amount" },
      orderCount: { $sum: 1 },
      avgAmount: { $avg: "$amount" }
  } },
  // 5. $sort + $limit 取前 5
  { $sort: { totalAmount: -1 } },
  { $limit: 5 },
  // 6. $project 重塑输出形状(1 保留 / 0 排除)
  { $project: {
      city: "$_id", totalAmount: 1, orderCount: 1,
      avgAmount: { $round: ["$avgAmount", 2] }, _id: 0
  } }
])
// 输出示例:
// { city: "北京", totalAmount: 158000, orderCount: 1203, avgAmount: 131.34 }
// { city: "上海", totalAmount: 132400, orderCount: 987,  avgAmount: 134.14 }
阶段作用性能提示
$match过滤文档放最前,能走索引
$lookup关联其他集合foreignField 需建索引
$unwind数组展开会复制文档,数组别太长
$group分组聚合大数据量走内存,超 100MB 限制用 allowDiskUse: true
$project重塑输出减少传输体积

聚合性能与内存限制

  • 100MB 内存上限:$group / $sort 默认最多用 100MB 内存,超限报错。数据量大时加 { allowDiskUse: true }(写临时文件,性能下降但可完成)
  • $match 永远放第一:尽早过滤减少下游处理量,且只有第一个 $match 能走索引(后续阶段的 $match 不行)
  • 先 $match 再 $lookup:$lookup 代价高,先过滤驱动集合;$lookup 的 foreignField 必须建索引,否则退化成全表扫描
  • 用 $limit 控制输出:分析类报表在管道尾部加 $limit,避免把全量结果传回应用
  • 滚动分页别用 $skip$skip + $limit 深偏移时性能骤降,滚动场景改用 _id > 上次末尾 的游标条件

4. 索引

# 创建索引(类似 MySQL 的 CREATE INDEX)
db.users.createIndex({ email: 1 }, { unique: true })  # 唯一索引
db.users.createIndex({ "address.city": 1, age: -1 })   # 复合索引
db.users.createIndex({ name: "text" })                  # 全文索引

# 查看索引
db.users.getIndexes()

# 删除索引
db.users.dropIndex("email_1")

索引规则与关系型数据库类似——最左前缀原则、避免过多索引、用 explain() 查看执行计划:

# 查看查询计划
db.users.find({ "address.city": "北京", age: { $gt: 25 } }).explain("executionStats")

索引类型与适用场景

类型创建方式适用
单字段createIndex({ age: 1 })单条件查询、排序
复合createIndex({ city: 1, age: -1 })多条件组合,遵守最左前缀
唯一createIndex({ email: 1 }, { unique: true })业务唯一性约束(email、身份证号)
TTLcreateIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })过期数据自动清理(会话、验证码、日志)
全文createIndex({ title: "text" })中文/英文搜索
稀疏/部分createIndex({ email: 1 }, { sparse: true })字段只在一部分文档中存在
// 索引策略实战:用户会话集合
db.sessions.createIndex({ token: 1 }, { unique: true })           // 登录态唯一
db.sessions.createIndex({ userId: 1, createdAt: -1 })             // 查用户会话列表
db.sessions.createIndex({ createdAt: 1 },
  { expireAfterSeconds: 604800 })                                  // 7 天自动过期

// 验证索引是否命中
db.sessions.find({ token: "abc123" }).explain("executionStats")
// winningPlan.stage = IXSCAN / FETCH(命中),COLLSCAN(全表扫描,未命中)

// 索引使用统计:找从未被使用的索引(accesses.ops 为 0 的)
db.aggregate([
  { $indexStats: {} },
  { $sort: { accesses.ops: 1 } }
])

TTL 索引是运维利器:后台线程每 60 秒扫描一次删除过期文档,删除量过大时用 mongod --setParameter ttlMonitorSleepSecs=600 降低频率。注意 TTL 只对日期类型生效,且必须是单字段索引。

复合索引设计:ESR 法则

复合索引列的顺序用 ESR 排列:Equality(等值条件)→ Sort(排序字段)→ Range(范围字段)。等值先行让索引尽快收窄,排序走索引免内存排序,范围放最后——因为范围条件一旦生效,后续列无法再参与匹配:

// 查询:某城市、按注册时间倒序、年龄在 20-30 之间
db.users.find({
  "address.city": "北京",           // Equality
  age: { $gte: 20, $lte: 30 }       // Range
}).sort({ createdAt: -1 })           // Sort

// 错误顺序(age 范围放中间,createdAt 的排序用不上索引):
db.users.createIndex({ "address.city": 1, age: 1, createdAt: -1 })

// 正确顺序:Equality → Sort → Range
db.users.createIndex({ "address.city": 1, createdAt: -1, age: 1 })
// 验证:explain() 的 winningPlan 出现 SORT 阶段 = 排序没走索引,调整列顺序

索引数量每多一个,写入成本多一份(每次写要维护索引树)。生产规则:单集合索引控制在 5-8 个以内,用 $indexStats 每季度清理一次零访问索引。

5. 数据建模

嵌入 vs 引用

策略示例适用
嵌入(Embed)用户文档直接包含地址1:1 或 1:N(数据量有限)
引用(Reference)订单引用用户 IDN:M 或数据独立更新频繁
# 嵌入模式(地址作为用户文档的一部分)
{
  name: "张三",
  addresses: [
    { type: "home", city: "北京", district: "海淀" },
    { type: "work", city: "上海", district: "浦东" }
  ]
}

# 引用模式(订单引用用户)
{
  orderId: "ORD-2025-001",
  userId: ObjectId("64a1b2c3d4e5f6a7b8c9d0e1"),
  items: [...]
}
# 关联查询
db.orders.aggregate([
  { $lookup: { from: "users", localField: "userId", foreignField: "_id", as: "user" } }
])
设计原则 优先嵌入——MongoDB 不支持 JOIN,嵌入避免多次查询。但如果嵌入数据无限增长(如日志数组),改用引用或拆分到独立集合。

6. 副本集与高可用

# 初始化 3 节点副本集
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "mongo1:27017", priority: 2 },
    { _id: 1, host: "mongo2:27017", priority: 1 },
    { _id: 2, host: "mongo3:27017", priority: 1 }
  ]
})

# 查看副本集状态
rs.status()

副本集特性:自动故障转移(Primary 宕机后 Secondary 自动选举)、读写分离(从 Secondary 读)、数据冗余。生产推荐至少 3 节点。

选举原理:心跳、优先级与多数派

  • 心跳:所有成员每 2 秒互发 heartbeat(可配 heartbeatIntervalSecs),连续 10 秒收不到 Primary 响应(electionTimeoutMillis 默认 10s)即宣布失联
  • 优先级priority 决定谁更适合当 Primary(数值大者优先,0 表示永不当 Primary),选举时高优先级节点会先发出候选请求
  • 多数派:任何投票(选举、心跳确认)必须拿到 n/2 + 1 票才能生效——3 节点需要 2 票,5 节点需要 3 票。这就是为什么偶数节点数没有意义(4 节点的多数派仍是 3,容错能力与 3 节点相同)
  • 选举触发:Primary 失联、节点优先级变化、网络分区后重连
// 完整投票成员配置示例:2 个可主节点 + 1 个隐藏节点 + 1 个仲裁节点
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "mongo1:27017", priority: 2, votes: 1 },
    { _id: 1, host: "mongo2:27017", priority: 1, votes: 1 },
    // hidden:不参与读、不接收业务流量,只做备份与延迟恢复
    { _id: 2, host: "mongo3:27017", priority: 0, hidden: true },
    // arbiter:不存数据,只投票(适合"两个数据中心 + 仲裁"架构)
    { _id: 3, host: "mongo4:27017", arbiterOnly: true }
  ]
})

// 手动触发选举(灰度发布 Primary 时用)
rs.stepDown(60)

// 查看每个成员的投票数与健康状态
rs.status().members.forEach(m => print(m.name, "votes:", m.votes,
  "state:", m.stateStr, "health:", m.health))
网络分区 3 节点副本集,如果 Primary 所在机房与另外 2 节点断开:失去多数派的旧 Primary 自动降级为 Secondary(拒绝写入),新机房选出新 Primary——保证任意时刻只有一个主,不会脑裂。客户端要配合 writeConcern: majority 才能读到一致数据。

7. 何时选择 MongoDB

场景推荐
文档结构频繁变化(schema 灵活)MongoDB
JSON/API 数据直接存储MongoDB
内容管理系统、用户画像MongoDB
需要强事务、复杂 JOINMySQL / PostgreSQL
金融、会计等强一致性场景MySQL / PostgreSQL
时间序列数据(日志、监控)TimescaleDB / InfluxDB

8. 运维操作:备份、恢复与监控

备份与恢复

# mongodump:逻辑备份(JSON/BSON 格式)
mongodump --uri="mongodb://backupuser:pass@localhost:27017/mydb" \
  --out=/backup/$(date +%F)            # 备份整个库
mongodump --uri="mongodb://localhost:27017" \
  --db=mydb --collection=orders --out=/backup/orders-only

# mongorestore:恢复
mongorestore --uri="mongodb://localhost:27017" \
  --drop /backup/2025-06-15            # --drop 先清空目标集合
mongorestore --uri="mongodb://localhost:27017" \
  --db=mydb /backup/orders-only/mydb/orders.bson

# 副本集场景:从 Secondary 备份,避免影响 Primary
mongodump --uri="mongodb://mongo2:27017/?replicaSet=rs0&readPreference=secondary" \
  --out=/backup/$(date +%F)
备份验证 备份没验证过等于没备份。恢复演练必须列入月度任务:随机抽一个备份文件恢复到隔离环境,跑一遍数据完整性校验(文档数对比、抽样字段比对)。

备份策略:逻辑备份 vs 快照 vs PITR

方式粒度恢复时间适用
mongodump(逻辑)库/集合分钟级小库(< 100GB)、跨版本迁移
文件系统快照(LVM/云盘)整机分钟级大库,配合 journal 保证一致性
oplog 增量 + 定期全量全量 + 增量按 oplog 窗口恢复点到"上次全量之后任意时刻"(PITR)
# 每日全量(凌晨 3 点,从 Secondary 备份,避免影响 Primary)+ 保留 30 天
0 3 * * * mongodump --uri="mongodb://mongo2:27017/?replicaSet=rs0&readPreference=secondary" \
  --out=/backup/full/$(date +%F) \
  && find /backup/full -mtime +30 -exec rm -rf {} +

# PITR 恢复(oplog 回放到指定时间点)
# 1. 恢复最近一次全量
mongorestore --uri="mongodb://localhost:27017" /backup/full/2025-06-15
# 2. 重放该时间点之后的 oplog,到目标时间点停止
# 3. 验证时间点前后数据一致性

# 防误删误改的轻量方案:延时备份节点(slaveDelay 2 小时)
rs.add({ _id: 4, host: "mongo5:27017", priority: 0, hidden: true, slaveDelay: 7200 })

监控命令

# mongostat:每秒输出操作数(类似 vmstat;MongoDB 7.0 列集,5.0 起已无 flushes 列)
mongostat --host localhost --discover -n 5
# insert query update delete getmore command dirty used vsize res qr|qw ar|aw netIn netOut conn time
#    22    180    15     0      0     4|0     20   1.8G  1.2G  0|0   0|1   32k   48k   12 01:02:03
# qr|qw:读/写队列——持续大于 0 说明并发超负荷

# mongotop:各集合读写耗时占比
mongotop 3

# 内置状态:连接数、缓存脏比例、副本集状态
db.serverStatus().connections
db.serverStatus().wiredTiger.cache["percentage cache dirty"]
rs.status()

生产监控要点:connections 与 ulimit(系统文件描述符上限要大于 maxIncomingConnections)、cache dirty 比例(WiredTiger 脏缓存过高说明写压力大,关注磁盘 I/O)、opcounters 的 insert/update 比例(写密集场景优先扩写能力)。

9. 分片集群:数据量再上一个量级

副本集解决高可用,分片解决"单机装不下 / 写不动"。架构三层:mongos(路由)、config servers(元数据)、shard 分片(每个分片本身是副本集)。

# 启用分片(以用户集合为例)
sh.enableSharding("myapp")
# 选择分片键:必须基数高、分布均匀、查询频率高
sh.shardCollection("myapp.users", { _id: "hashed" })        # 哈希分片(推荐,分布最均匀)
# 或范围分片(有数据热点风险):
# sh.shardCollection("myapp.orders", { createdAt: 1 })
# 注意:分片键必须是集合中真实存在的字段(本教程 users 无 userId 字段)

# 查看分片分布与 chunk 迁移
sh.status()

# 分片键选择三原则:
# 1. 高频查询必须带分片键(否则广播查询,性能灾难)
# 2. 取值分布均匀(hashed 分片最稳)
# 3. 不可变(分片键更新受限,选错了只能迁移)
分片键是单行道 MongoDB 的分片键一旦设置不可修改(官方不提供改键工具)。上线前用生产数据形态模拟查询分布,多花一天验证,避免上线后重建集群。

常见错误

问题表现排查方法
文档过大插入报错 BSONObjectTooLarge(16MB 限制)检查文档是否嵌入了过大的数组或二进制数据,改用 GridFS 存储大文件或拆分引用
索引未生效explain() 显示 stage=IXSCAN 但 docsExamined 接近全表检查查询字段顺序是否符合复合索引最左前缀,是否用了 $ne / $not 等不走索引的操作符
内存占用过高mongod 内存持续增长,系统开始 swap增大 wiredTiger.cacheSizeGB,确认 workingSet 大小,检查是否有未命中的全表扫描
连接数耗尽报错 connection pool exhausted检查 maxIncomingConnections 配置,确认应用侧连接池大小合理,排查连接泄漏
副本集选举卡顿Primary 宕机后长时间无新 Primary确认节点数 ≥ 3(奇数),检查 priority 和 votes 配置,排查网络延迟
备份恢复后副本集不同步mongorestore 后 Secondary 与 Primary 数据不一致恢复前确认 --drop 行为与 oplog 位置;大库恢复优先用文件系统快照 + oplog 追平
聚合报内存超限ExceededMemoryLimit(100MB 上限)管道加 { allowDiskUse: true };优化管道把 $match 提前、去掉冗余 $unwind

最佳实践

  1. 优先嵌入:1:1 或 1:N 关系优先用嵌入文档,减少查询次数;嵌入数据超过 100KB 时考虑拆分。
  2. 建索引前先 explain:用 explain("executionStats") 验证查询是否真的需要索引,避免创建无用索引拖慢写入。
  3. _id 设计:默认 ObjectId 包含时间戳,适合一般场景;如需自定义排序,可在业务字段上建索引。
  4. 生产必须认证:启用 authorization: enabled 并为应用创建专用 readWrite 用户,不要用 admin 账户直连。
  5. 副本集至少 3 节点:2 节点无法实现多数派选举,生产环境至少 3 节点(1 Primary + 2 Secondary)。
  6. 复合索引用 ESR 排列:Equality → Sort → Range,等值先行、排序走索引、范围放最后。
  7. 备份分级 + 月度恢复演练:全量 + oplog 增量组合实现 PITR,每月抽一个备份恢复到隔离环境验证。

练习题

  1. 创建一个博客文章集合(posts),设计嵌入式评论和引用式标签两种数据模型,分别写出插入和查询的命令,对比各自优劣。
  2. 为 posts 集合创建一个复合索引,覆盖「按作者查询 + 按发布时间倒序 + 只返回标题」的场景,用 explain() 验证是否走了 Index Only Scan。
  3. 用聚合管道实现一个「按标签统计文章数,取前 5 名」的查询,写出完整命令并解释每个阶段的作用。
  4. 设计一个"库存扣减 + 订单创建"的多文档事务,用条件检查防止超扣,并解释为什么不能用两个独立的 updateOne。
  5. 为你的业务设计备份方案:选择备份方式(逻辑/快照/oplog 增量)、cron 策略、保留周期,并写出月度恢复演练的检查清单。

学习检查点

学完本章后,请检验自己是否掌握以下内容:

检查项自测问题验证方法
概念理解能用自己的话解释 MongoDB 文档模型与关系型数据库的区别尝试向他人讲解
命令操作能不查文档完成 MongoDB 集合操作、文档 CRUD、索引创建在终端实际执行
原理掌握能说出 MongoDB 的 BSON 存储格式和 WiredTiger 存储引擎原理画出流程图
故障排查能独立排查 MongoDB 连接错误、查询性能问题模拟故障并修复
最佳实践能说明为什么需要为 MongoDB 配置副本集和分片集群对比不同方案

本章总结

MongoDB 用文档模型换来了 schema 灵活性与水平扩展能力,代价是放弃了事务的强一致性和跨集合 JOIN。选型的关键不是"哪个更好",而是"数据形态适合哪种":强一致、多表关联选 MySQL/PostgreSQL;JSON 形态、读写比例高、需要水平扩展选 MongoDB。无论哪种,索引和副本集都是生产底线。

延伸阅读

↑ 回到顶部