MongoDB 从入门到生产:建模、索引、聚合、事务、分片与运维实战(含 Node.js/Python 示例)
MongoDB 从入门到生产:建模、索引、聚合、事务、分片与运维实战(含 Node.js/Python 示例)
适读人群:后端/全栈/数据工程开发者,既要快速上手也要能稳定上生产
阅读收获:
- 搞清 MongoDB 的文档模型与正确的建模方式(嵌入 vs 参照)
- 会用索引与聚合管道写出高性能查询
- 掌握事务、Change Streams、TTL、全文检索、地理空间等高级能力
- 按文档一步步完成部署、备份、监控与调优
文章目录
- MongoDB 从入门到生产:建模、索引、聚合、事务、分片与运维实战(含 Node.js/Python 示例)
- @[toc]
- 0. MongoDB 是什么(以及何时用/不用)
- 1. 安装与启动(3种最快方式)
- 1.1 Docker 一键起
- 1.2 本地安装
- 1.3 云托管
- 2. 核心概念快速过一遍
- 3. 5分钟 CRUD 入门(mongosh)
- 4. 正确的数据建模(Embedding vs Referencing)
- 4.1 何时**嵌入(Embed)**
- 4.2 何时**参照(Reference)**
- 4.3 经典模式
- 5. 索引:性能的 80%
- 5.1 基本类型
- 5.2 文本/地理/哈希/通配
- 5.3 查看与调优
- 6. 聚合管道(Aggregation Pipeline)实战
- 6.1 销售日报(分组 + 展开 + 投影)
- 6.2 关联查询($lookup 管道写法)
- 6.3 多结果面板($facet)
- 7. 事务(Transactions)与一致性
- 8. 高级能力一锅端
- 8.1 Change Streams(变更订阅)
- 8.2 Time Series Collection(时间序列)
- 8.3 全文搜索/地理空间
- 8.4 TTL/软删除/历史版本
- 9. Node.js / Python 驱动示例
- 9.1 Node.js(官方驱动)
- 9.2 Python(pymongo)
- 10. 复制集(高可用)与分片(水平扩展)
- 10.1 副本集(Replica Set)最小示例(Docker Compose)
- 10.2 分片(Sharding)要点
- 11. 安全与权限
- 12. 备份/恢复与迁移
- 13. 调优与排错清单(实战有效)
- 14. 学习与实践路线(建议)
- 15. 附:示例数据与一键脚本
- 15.1 快速造数(mongosh)
- 15.2 常用命令速查
- 结语
文章目录
- MongoDB 从入门到生产:建模、索引、聚合、事务、分片与运维实战(含 Node.js/Python 示例)
- @[toc]
- 0. MongoDB 是什么(以及何时用/不用)
- 1. 安装与启动(3种最快方式)
- 1.1 Docker 一键起
- 1.2 本地安装
- 1.3 云托管
- 2. 核心概念快速过一遍
- 3. 5分钟 CRUD 入门(mongosh)
- 4. 正确的数据建模(Embedding vs Referencing)
- 4.1 何时**嵌入(Embed)**
- 4.2 何时**参照(Reference)**
- 4.3 经典模式
- 5. 索引:性能的 80%
- 5.1 基本类型
- 5.2 文本/地理/哈希/通配
- 5.3 查看与调优
- 6. 聚合管道(Aggregation Pipeline)实战
- 6.1 销售日报(分组 + 展开 + 投影)
- 6.2 关联查询($lookup 管道写法)
- 6.3 多结果面板($facet)
- 7. 事务(Transactions)与一致性
- 8. 高级能力一锅端
- 8.1 Change Streams(变更订阅)
- 8.2 Time Series Collection(时间序列)
- 8.3 全文搜索/地理空间
- 8.4 TTL/软删除/历史版本
- 9. Node.js / Python 驱动示例
- 9.1 Node.js(官方驱动)
- 9.2 Python(pymongo)
- 10. 复制集(高可用)与分片(水平扩展)
- 10.1 副本集(Replica Set)最小示例(Docker Compose)
- 10.2 分片(Sharding)要点
- 11. 安全与权限
- 12. 备份/恢复与迁移
- 13. 调优与排错清单(实战有效)
- 14. 学习与实践路线(建议)
- 15. 附:示例数据与一键脚本
- 15.1 快速造数(mongosh)
- 15.2 常用命令速查
- 结语
0. MongoDB 是什么(以及何时用/不用)
- 文档数据库:数据以 BSON(Binary JSON) 文档存储,天然适合 JSON。
- 优点:灵活 schema、数组/嵌套对象、快速迭代、丰富索引与聚合、易横向扩展(分片)。
- 避免:强事务一致性、跨表复杂 JOIN 特别多的 OLTP 场景(可使用事务,但不应滥用)。
1. 安装与启动(3种最快方式)
1.1 Docker 一键起
docker run -d --name mongo \-p 27017:27017 \-v $PWD/mongo-data:/data/db \-e MONGO_INITDB_ROOT_USERNAME=admin \-e MONGO_INITDB_ROOT_PASSWORD=secret \mongo:7
连接:
docker exec -it mongo mongosh -u admin -p secret
1.2 本地安装
- Windows/macOS:从官网安装
mongod
+mongosh
- Linux:使用官方 apt/yum 源安装
mongodb-org
1.3 云托管
- MongoDB Atlas:创建免费集群,抄连接串即可(适合新手与小项目)
2. 核心概念快速过一遍
- 数据库(database)→ 集合(collection)→ 文档(document)
_id
:主键,默认ObjectId
(包含时间戳,可用于粗粒度排序)- BSON 类型:
string/number/int64/decimal128/date/array/object/binary/objectId/bool
等 - 写关注(writeConcern):
w:1/majority
;读偏好(readPreference):primary/secondary - 单文档原子性:对一个文档的修改是原子的;跨文档需要事务
3. 5分钟 CRUD 入门(mongosh)
use shop// 插入
db.products.insertMany([{ _id: 1, name: "Keyboard", price: 199, stock: 20, tags: ["peripheral","hot"] },{ _id: 2, name: "Mouse", price: 129, stock: 50, tags: ["peripheral"] }
])// 查询(条件 + 投影 + 排序 + 分页)
db.products.find({ price: { $gte: 150 } }, { name: 1, price: 1, _id: 0 }).sort({ price: -1 }).skip(0).limit(10)// 更新($set/$inc/$push/$addToSet/upsert)
db.products.updateOne({ _id: 1 }, { $inc: { stock: -1 }, $set: { updatedAt: new Date() } })
db.products.updateOne({ _id: 3 }, { $set: { name: "Pad", price: 99, stock: 100 }}, { upsert: true })// 删除
db.products.deleteMany({ stock: { $lte: 0 }})
4. 正确的数据建模(Embedding vs Referencing)
4.1 何时嵌入(Embed)
- 一对少(小数组)、强一致性读取、总是一起读取
- 例:订单中嵌入订单项(拍下时复制商品快照)
// orders
{_id: ObjectId("..."),userId: 1001,items: [{ skuId: 11, name: "Keyboard", price: 199, qty: 1 },{ skuId: 22, name: "Mouse", price: 129, qty: 2 }],status: "Paid", createdAt: ISODate(...)
}
4.2 何时参照(Reference)
- 多对多 / 数组非常大 / 对象需要独立更新
- 例:评论集合
comments
参照postId
和userId
(在读时$lookup
或分两次查)
4.3 经典模式
- Subset Pattern:在父文档里只保留子项前 N 条摘要,其余分页查子集合
- Bucket Pattern:时间序列按时间段装桶,减少文档数量
- Outlier Pattern:异常大数据单独放置,避免多数文档过大
- Schema Validation:用 JSON Schema 约束字段
db.createCollection("users", {validator: {$jsonSchema: {bsonType: "object",required: ["email", "createdAt"],properties: {email: { bsonType: "string", pattern: "^\\S+@\\S+$" },age: { bsonType: "int", minimum: 0 }}}}
})
5. 索引:性能的 80%
目标:高选择性字段 + 覆盖常用查询和排序,并控制索引数量(写入成本)。
5.1 基本类型
- 单键:
db.col.createIndex({ email: 1 }, { unique: true })
- 复合:前缀规则——
{a:1,b:1,c:1}
可用于(a)
/(a,b)
/(a,b,c)
- 部分索引:
{ partialFilterExpression: { status: "ACTIVE" } }
- 稀疏索引:仅索引存在该字段的文档
- TTL:自动过期删除(如会话/临时数据)
db.sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 })
5.2 文本/地理/哈希/通配
db.articles.createIndex({ content: "text", title: "text" }, { default_language: "english" })
db.shops.createIndex({ location: "2dsphere" })
db.logs.createIndex({ userId: "hashed" }) // 分片常见
db.dynamic.createIndex({ "$**": 1 }) // wildcard(谨慎)
5.3 查看与调优
db.col.getIndexes()
db.col.dropIndex("name_1")db.col.find({ status: "PAID" }).sort({ createdAt: -1 }).hint({ status: 1, createdAt: -1 }) // 强制索引(仅排错时).explain("executionStats") // 看实际扫描数 nReturned/totalDocsExamined
定位慢查询:开启 profiler(或在 Atlas/监控里看)
db.setProfilingLevel(1, { slowms: 50 }) // 记录 >50ms 的查询
db.system.profile.find().sort({ ts:-1 }).limit(5)
6. 聚合管道(Aggregation Pipeline)实战
6.1 销售日报(分组 + 展开 + 投影)
db.orders.aggregate([{ $unwind: "$items" },{ $group: {_id: { day: { $dateToString: { format: "%Y-%m-%d", date: "$createdAt" } }, skuId: "$items.skuId" },qty: { $sum: "$items.qty" },amt: { $sum: { $multiply: [ "$items.price", "$items.qty" ] } }}},{ $sort: { "_id.day": 1 } }
])
6.2 关联查询($lookup 管道写法)
db.orders.aggregate([{ $match: { userId: 1001 } },{ $lookup: {from: "users",let: { uid: "$userId" },pipeline: [{ $match: { $expr: { $eq: ["$_id","$$uid"] } } }, { $project: { email:1, _id:0 } }],as: "user"}},{ $set: { user: { $first: "$user.email" } } }
])
6.3 多结果面板($facet)
db.orders.aggregate([{ $facet: {topUsers: [ { $group: { _id: "$userId", amt:{ $sum:"$total" } } }, { $sort:{ amt:-1 } }, { $limit:5 } ],byStatus: [ { $group: { _id: "$status", n:{ $sum:1 } } } ],recent: [ { $sort:{ createdAt:-1 } }, { $limit:10 }, { $project:{ _id:1,total:1 } } ]}}
])
7. 事务(Transactions)与一致性
跨文档/集合原子性更新:自 4.0 起支持 多文档 ACID 事务(副本集/分片集群)。
Node.js 示例(mongodb
官方驱动)
import { MongoClient } from "mongodb"
const client = new MongoClient(process.env.MONGO_URL)
await client.connect()
const session = client.startSession()await session.withTransaction(async () => {const orders = client.db("shop").collection("orders")const wallet = client.db("shop").collection("wallets")await orders.insertOne({ userId: 1001, total: 199, createdAt: new Date() }, { session })const ok = await wallet.updateOne({ userId: 1001, balance: { $gte: 199 } },{ $inc: { balance: -199 } }, { session })if (ok.matchedCount === 0) throw new Error("insufficient")
}, {readConcern: { level: "snapshot" },writeConcern: { w: "majority" }
})
await session.endSession()
最佳实践
- 事务越短越好;避免长事务与大批量文档(会占用内存与 oplog)
- 在业务允许的情况下,优先单文档原子或流水表/幂等设计替代跨文档事务
8. 高级能力一锅端
8.1 Change Streams(变更订阅)
const col = client.db("shop").collection("orders")
const cursor = col.watch([{ $match: { "fullDocument.status": "Paid" } }])
for await (const change of cursor) {console.log("paid order:", change.fullDocument._id)
}
8.2 Time Series Collection(时间序列)
db.createCollection("metrics", {timeseries: { timeField: "ts", metaField: "host", granularity: "minutes" }
})
db.metrics.insertOne({ host: "api-1", ts: new Date(), cpu: 0.53, mem: 0.71 })
8.3 全文搜索/地理空间
db.blog.createIndex({ title:"text", body:"text" })
db.blog.find({ $text: { $search: "\"vector index\" -deprecated" } }) // 短语 + 排除db.place.createIndex({ loc:"2dsphere" })
db.place.find({loc: { $near: { $geometry: { type:"Point", coordinates:[116.39, 39.9] }, $maxDistance: 2000 } }
})
8.4 TTL/软删除/历史版本
- 会话/验证码:TTL 索引自动清理
- 业务软删:加
deletedAt
字段 + 过滤索引(partial index) - 历史版本:主集合 + 历史集合
$merge
归档
9. Node.js / Python 驱动示例
9.1 Node.js(官方驱动)
import { MongoClient } from "mongodb"
const client = new MongoClient("mongodb://admin:secret@localhost:27017/?authSource=admin")
await client.connect()
const users = client.db("shop").collection("users")await users.createIndex({ email: 1 }, { unique: true })
await users.insertOne({ email:"a@b.com", createdAt:new Date() })
const u = await users.findOne({ email:"a@b.com" }, { projection:{ email:1, _id:0 }})
console.log(u)
await client.close()
9.2 Python(pymongo)
from pymongo import MongoClient, ASCENDING
client = MongoClient("mongodb://admin:secret@localhost:27017/?authSource=admin")
col = client.shop.users
col.create_index([("email", ASCENDING)], unique=True)
col.update_one({"email":"a@b.com"}, {"$set":{"updatedAt":True}}, upsert=True)
for u in col.find({}, {"_id":0, "email":1}).limit(3):print(u)
client.close()
连接池:服务端默认有连接池;在 Node.js 设置 maxPoolSize
,在 Python 用 MongoClient(maxPoolSize=…)
10. 复制集(高可用)与分片(水平扩展)
10.1 副本集(Replica Set)最小示例(Docker Compose)
# docker-compose.yml
services:mongo1: { image: mongo:7, command: ["--replSet","rs0"], ports: ["27017:27017"] }mongo2: { image: mongo:7, command: ["--replSet","rs0"] }mongo3: { image: mongo:7, command: ["--replSet","rs0"] }
初始化:
rs.initiate({ _id:"rs0", members:[{ _id:0, host:"mongo1:27017" },{ _id:1, host:"mongo2:27017" },{ _id:2, host:"mongo3:27017" }
]})
10.2 分片(Sharding)要点
- 分片键决定路由与均衡:
- 哈希键:分布均匀,适合写多
- 范围键:支持范围查询,但可能热点
- 提前规划:一旦上线再换分片键非常困难(需要重分片工具/迁移)
11. 安全与权限
- 启用认证:
--auth
或 Docker 环境变量初始化 - 创建应用用户(最小权限):
use admin
db.createUser({ user:"app", pwd:"app_pwd", roles:[ { role:"readWrite", db:"shop" } ] })
- 网络:绑定内网地址
--bind_ip
,对外开启 TLS;配置备份账户只读权限 - 客户端加密(FLE)可对敏感字段加密(有性能成本)
12. 备份/恢复与迁移
mongodump --uri "mongodb://admin:secret@host/shop" -o ./bak
mongorestore --uri "mongodb://admin:secret@host" ./bak# 导入/导出 JSON/CSV
mongoimport --db shop --collection users --file users.json --jsonArray
mongoexport --db shop --collection users --out users.json --jsonArray
13. 调优与排错清单(实战有效)
- 一定要建索引:所有过滤条件、关联键、排序用到的字段
- 只取需要的字段:使用
projection
,减少网络/解码成本 - 避免“替换式更新”:
updateOne(doc)
不带$set
会整个替换文档 - 类型一致:同一字段保持同一类型,避免索引失效
- 文档大小 < 16MB;大数组用 Bucket/Subset 模式
- 批量写:
bulkWrite
;读多写少用缓存/副本集只读 - Explain 常看:
executionStats
中totalDocsExamined
应远小于nReturned
- 连接池:设置
maxPoolSize
,并正确关闭闲置连接
14. 学习与实践路线(建议)
- 用 Docker 起一个本地实例 + mongosh 熟悉 CRUD
- 跑 3 个典型聚合:销售日报 / 用户排行 / 文本搜索
- 为项目补上:必需索引 + Schema 校验 + TTL
- 将关键业务改成:单文档原子/幂等 或 短事务
- 上线前:副本集 + 备份脚本 + 慢查监控 + 压测
15. 附:示例数据与一键脚本
15.1 快速造数(mongosh)
use shop
db.orders.drop()
for (let i=0;i<1000;i++){const uid = 1000 + Math.floor(Math.random()*50)const n = 1 + Math.floor(Math.random()*4)const items = Array.from({length:n},()=>({skuId: 10+Math.floor(Math.random()*10),price: 50 + Math.floor(Math.random()*200),qty: 1 + Math.floor(Math.random()*3)}))const total = items.reduce((s,x)=>s+x.price*x.qty,0)db.orders.insertOne({ userId: uid, items, total, status: "Paid", createdAt: new Date(Date.now()-Math.random()*86400000) })
}
db.orders.createIndex({ userId:1, createdAt:-1 })
15.2 常用命令速查
show dbs | use db | show collections
db.col.stats() // 集合统计
db.serverStatus() // 实例健康
db.currentOp() // 当前操作
db.killOp(opid) // 终止
结语
MongoDB 真正的门槛在于建模与索引。把握“经常一起取就嵌入、增长无界就拆分”的原则,围绕高选择性索引 + 聚合管道组织查询,再用副本集/备份/监控兜底,你就能把它稳稳地用到生产。如果你愿意,我可以把文中的示例和 Docker 配置打包成一个可下载模板,或者根据你的业务模型定制索引与聚合方案。