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、身份证号) |
| TTL | createIndex({ 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) | 订单引用用户 ID | N: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" } }
])
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))
writeConcern: majority 才能读到一致数据。7. 何时选择 MongoDB
| 场景 | 推荐 |
|---|---|
| 文档结构频繁变化(schema 灵活) | MongoDB |
| JSON/API 数据直接存储 | MongoDB |
| 内容管理系统、用户画像 | MongoDB |
| 需要强事务、复杂 JOIN | MySQL / 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. 不可变(分片键更新受限,选错了只能迁移)
常见错误
| 问题 | 表现 | 排查方法 |
|---|---|---|
| 文档过大 | 插入报错 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:N 关系优先用嵌入文档,减少查询次数;嵌入数据超过 100KB 时考虑拆分。
- 建索引前先 explain:用 explain("executionStats") 验证查询是否真的需要索引,避免创建无用索引拖慢写入。
- _id 设计:默认 ObjectId 包含时间戳,适合一般场景;如需自定义排序,可在业务字段上建索引。
- 生产必须认证:启用 authorization: enabled 并为应用创建专用 readWrite 用户,不要用 admin 账户直连。
- 副本集至少 3 节点:2 节点无法实现多数派选举,生产环境至少 3 节点(1 Primary + 2 Secondary)。
- 复合索引用 ESR 排列:Equality → Sort → Range,等值先行、排序走索引、范围放最后。
- 备份分级 + 月度恢复演练:全量 + oplog 增量组合实现 PITR,每月抽一个备份恢复到隔离环境验证。
练习题
- 创建一个博客文章集合(posts),设计嵌入式评论和引用式标签两种数据模型,分别写出插入和查询的命令,对比各自优劣。
- 为 posts 集合创建一个复合索引,覆盖「按作者查询 + 按发布时间倒序 + 只返回标题」的场景,用 explain() 验证是否走了 Index Only Scan。
- 用聚合管道实现一个「按标签统计文章数,取前 5 名」的查询,写出完整命令并解释每个阶段的作用。
- 设计一个"库存扣减 + 订单创建"的多文档事务,用条件检查防止超扣,并解释为什么不能用两个独立的 updateOne。
- 为你的业务设计备份方案:选择备份方式(逻辑/快照/oplog 增量)、cron 策略、保留周期,并写出月度恢复演练的检查清单。
学习检查点
学完本章后,请检验自己是否掌握以下内容:
| 检查项 | 自测问题 | 验证方法 |
|---|---|---|
| 概念理解 | 能用自己的话解释 MongoDB 文档模型与关系型数据库的区别 | 尝试向他人讲解 |
| 命令操作 | 能不查文档完成 MongoDB 集合操作、文档 CRUD、索引创建 | 在终端实际执行 |
| 原理掌握 | 能说出 MongoDB 的 BSON 存储格式和 WiredTiger 存储引擎原理 | 画出流程图 |
| 故障排查 | 能独立排查 MongoDB 连接错误、查询性能问题 | 模拟故障并修复 |
| 最佳实践 | 能说明为什么需要为 MongoDB 配置副本集和分片集群 | 对比不同方案 |
本章总结
MongoDB 用文档模型换来了 schema 灵活性与水平扩展能力,代价是放弃了事务的强一致性和跨集合 JOIN。选型的关键不是"哪个更好",而是"数据形态适合哪种":强一致、多表关联选 MySQL/PostgreSQL;JSON 形态、读写比例高、需要水平扩展选 MongoDB。无论哪种,索引和副本集都是生产底线。
延伸阅读
- 3.6:MySQL 数据库 MySQL/MariaDB 数据库安装与管理
- 3.7:PostgreSQL 数据库 PostgreSQL 数据库安装与管理
- db01 SQL 优化与索引调优