MongoDB 基础与实战
核心概念对比(与 RDBMS)
| RDBMS | MongoDB | 说明 |
|---|---|---|
| Database | Database | 数据库 |
| Table | Collection | 集合(隐式创建) |
| Row | Document | 文档(BSON 格式) |
| Column | Field | 字段 |
| Primary Key | _id | 自动生成 ObjectId |
| Index | Index | 支持多种索引 |
| JOIN | $lookup / 嵌入文档 | 聚合管道的关联查询 |
BSON 文档结构
javascript
{
"_id": ObjectId("618..."),
"title": "MongoDB 入门",
"tags": ["database", "nosql"],
"author": {
"name": "Alice",
"email": "alice@example.com"
},
"views": 1024,
"createdAt": ISODate("2025-01-01T00:00:00Z")
}数据以 BSON(Binary JSON)格式存储,支持 Date、ObjectId、Binary Data 等 JSON 没有的类型。
CRUD 操作
插入
javascript
// 插入单条
db.users.insertOne({
name: "Alice",
email: "alice@example.com",
age: 25
})
// 批量插入
db.users.insertMany([
{ name: "Bob", age: 30 },
{ name: "Charlie", age: 35 }
])
// 插入时指定 _id
db.users.insertOne({ _id: "user_001", name: "Diana" })查询
javascript
// 精确匹配
db.users.find({ name: "Alice" })
// 比较操作符
db.users.find({ age: { $gte: 18, $lte: 60 } })
db.users.find({ age: { $in: [25, 30, 35] } })
// 逻辑操作符
db.users.find({ $or: [{ age: 25 }, { name: "Bob" }] })
db.users.find({ age: { $not: { $gte: 60 } } })
// 正则查询
db.users.find({ name: /^A/ })
// 字段筛选(投影)
db.users.find({}, { name: 1, email: 1, _id: 0 })
// 计数
db.users.countDocuments({ age: { $gte: 18 } })
// 分页
db.users.find().skip(20).limit(10).sort({ age: -1 })更新
javascript
// 更新单个文档
db.users.updateOne(
{ _id: ObjectId("...") },
{ $set: { age: 26 } }
)
// 批量更新
db.users.updateMany(
{ age: { $lt: 18 } },
{ $set: { status: "minor" } }
)
// 替换整个文档
db.users.replaceOne(
{ _id: ObjectId("...") },
{ name: "Alice", age: 27 }
)
// 原子增减
db.articles.updateOne(
{ _id: ObjectId("...") },
{ $inc: { views: 1 } }
)
// 数组操作
db.articles.updateOne(
{ _id: ObjectId("...") },
{ $push: { tags: "mongodb" } }
)
db.articles.updateOne(
{ _id: ObjectId("...") },
{ $pull: { tags: "obsolete" } }
)删除
javascript
db.users.deleteOne({ _id: ObjectId("...") })
db.users.deleteMany({ status: "inactive" })
db.users.deleteMany({}) // 清空集合
// 删除集合(包括所有索引)
db.users.drop()索引
索引类型
javascript
// 单字段索引(升序 1 / 降序 -1)
db.users.createIndex({ email: 1 })
// 复合索引
db.users.createIndex({ age: 1, name: 1 })
// 注意:字段顺序影响查询匹配
// 唯一索引
db.users.createIndex({ email: 1 }, { unique: true })
// 多键索引(数组字段)
db.articles.createIndex({ tags: 1 }) // tags 是数组
// 文本索引(全文搜索)
db.articles.createIndex({ title: "text", content: "text" })
db.articles.find({ $text: { $search: "mongodb" } })
// 哈希索引(分片键常用)
db.users.createIndex({ _id: "hashed" })
// TTL 索引(自动过期)
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
// 地理空间索引(2dsphere)
db.places.createIndex({ location: "2dsphere" })
db.places.find({
location: {
$near: {
$geometry: { type: "Point", coordinates: [116.40, 39.90] },
$maxDistance: 1000 // 单位: 米
}
}
})EXPLAIN 分析
javascript
db.users.find({ age: { $gte: 18 } }).explain("executionStats")
// 输出包含: IXSCAN / COLLSCAN、nReturned、totalDocsExamined聚合管道(Aggregation Pipeline)
聚合管道是 MongoDB 最强大的数据处理功能,数据依次经过多个阶段处理:
javascript
db.orders.aggregate([
// Stage 1: 筛选
{ $match: { status: "completed", createdAt: { $gte: ISODate("2026-01-01") } } },
// Stage 2: 分组统计
{ $group: {
_id: "$category",
totalSales: { $sum: "$amount" },
avgPrice: { $avg: "$amount" },
count: { $sum: 1 }
}},
// Stage 3: 排序
{ $sort: { totalSales: -1 } },
// Stage 4: 限制条数
{ $limit: 10 },
// Stage 5: 投影字段
{ $project: {
category: "$_id",
totalSales: 1,
avgPrice: { $round: ["$avgPrice", 2] },
_id: 0
}}
])常用阶段
| 阶段 | 作用 | 类比 SQL |
|---|---|---|
$match | 筛选文档 | WHERE |
$project | 字段投影 | SELECT |
$group | 分组聚合 | GROUP BY |
$sort | 排序 | ORDER BY |
$limit / $skip | 分页 | LIMIT / OFFSET |
$unwind | 数组展开 | 无(展开成多行) |
$lookup | 跨集合关联 | LEFT JOIN |
$addFields | 添加计算字段 | 无 |
$bucket | 分桶统计 | CASE WHEN |
$lookup 示例
javascript
db.orders.aggregate([
{ $lookup: {
from: "products", // 关联集合
localField: "productId", // 本地字段
foreignField: "_id", // 关联字段
as: "product" // 输出数组字段
}},
{ $unwind: "$product" } // 展开为对象
])复制集(Replica Set)
架构
text
┌───────────────┐
│ Primary (主) │ ← 读写
└───────┬───────┘
│ Oplog 复制
┌──────────┼──────────┐
│ │ │
┌───┴────┐ ┌──┴────┐ ┌──┴────┐
│Secondary│ │Secondary│ │ Arbiter│
│ 读 │ │ 读 │ │ 投票 │
└────────┘ └────────┘ └────────┘推荐部署:1 Primary + 2 Secondary(3 节点) 最小部署:1 Primary + 1 Secondary + 1 Arbiter(仲裁者,不存数据)
故障转移
text
Primary 宕机 → 所有 Secondary 心跳超时
→ 选举新的 Primary(Raft 算法)
→ 旧 Primary 恢复后自动降级为 Secondary
选举优先级:
1. members[n].priority 最高
2. oplog 最接近 Primary
3. 投票表决(多数派)读写关注
javascript
// 读关注(readConcern)
db.collection.find().readConcern("majority")
// local: 读本地(默认,可能回滚)
// majority: 读已提交到多数节点的数据(不会回滚)
// 写关注(writeConcern)
db.collection.insertOne(doc, { writeConcern: { w: "majority" } })
// w: 1 → 等待 Primary 确认(默认)
// w: majority → 等待多数节点确认(安全)
// w: 3 → 等待 3 个节点确认
// j: true → 等待写入 journalMongoose(Node.js ODM)
模型定义
javascript
const mongoose = require('mongoose')
const userSchema = new mongoose.Schema({
name: { type: String, required: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
age: { type: Number, min: 0, max: 150 },
role: { type: String, enum: ['user', 'admin'], default: 'user' },
tags: [String],
address: {
city: String,
zip: String
},
createdAt: { type: Date, default: Date.now }
})
// 虚拟字段
userSchema.virtual('isAdult').get(function() {
return this.age >= 18
})
// 中间件(钩子)
userSchema.pre('save', function(next) {
this.updatedAt = new Date()
next()
})
const User = mongoose.model('User', userSchema)查询
javascript
// 普通查询
const users = await User.find({ age: { $gte: 18 } })
.sort({ age: -1 })
.limit(10)
.select('name email')
.lean() // 返回普通 JS 对象,比 Mongoose Document 快
// 聚合
const stats = await User.aggregate([
{ $group: { _id: '$role', count: { $sum: 1 } } }
])索引定义
javascript
userSchema.index({ email: 1 }, { unique: true })
userSchema.index({ age: 1, name: 1 })设计最佳实践
Schema 设计三要素
- 嵌入 vs 引用
| 适合嵌入 | 适合引用 |
|---|---|
| 一对一关系(如用户资料) | 多对多关系(如文章与标签) |
| 少量子文档(< 100) | 大量独立数据(如日志) |
| 子文档几乎不独立查询 | 子文档需要单独查询 |
| 数据一致性要求高 | 数据独立更新频繁 |
- 反范式化
- 冗余热点字段减少关联查询
- 如文章集合直接存评论数,而非每次 COUNT
- 预聚合
- 预先计算的统计数据存在单独的集合
- 避免运行时大表聚合
性能优化
text
- 确保查询走索引(explain("executionStats") 检查)
- 使用 projection 只返回必要字段
- 批量操作使用 bulkWrite()
- 大集合建立索引时 background: true
- 避免未使用索引的 sort(内存排序限制 32 MB)
- TTL 索引自动清理过期数据