MongoDB 文档型数据库——灵活结构的代表 / MongoDB and Flexible Document Data Models
📅 创建时间:2026-05-08 🏷️ 标签:#MongoDB #文档型 #AggregationPipeline #副本集 #mongoose #Schema-less 📚 前置知识:[[00-db-overview]] [[01-mysql]](了解关系型数据库基础) 📚 相关知识:[[06-kv-embedded]](NoSQL 家族对比)
MongoDB 定位速览
┌─────────────────────────────────────────────────────────────┐
│ MongoDB 在数据库版图中的位置 │
├─────────────────────────────────────────────────────────────┤
│ │
│ GitHub Stars: 26K+,全球最流行的文档型数据库 │
│ 定位: Schema-less 的 JSON 文档数据库 │
│ 核心哲学: "数据应该像 JavaScript 对象一样灵活" │
│ 适用场景: 内容管理、UGC、快速迭代的产品、无固定结构的数据 │
│ 不适合: 强关联/强事务场景、需要复杂 JOIN 的分析场景 │
│ │
└─────────────────────────────────────────────────────────────┘第1部分:文档模型——思维转换
1.1 关系型 vs 文档型
┌─────────────────────────────────────────────────────────────┐
│ 关系型 vs 文档型思维差异 │
├─────────────────────────────────────────────────────────────┤
│ │
│ MySQL(关系型): │
│ │
│ users ──1:N──► orders ──N:1──► products │
│ │
│ 3 张表 + JOIN + 外键约束 │
│ 优点:结构清晰、事务完整、JOIN 强大 │
│ 缺点:改结构代价大、JSON 数据要拆成多表 │
│ │
│ MongoDB(文档型): │
│ │
│ users 文档: │
│ { │
│ _id: ObjectId, │
│ name: "张三", │
│ orders: [ │
│ { │
│ order_id: "ORD001", │
│ items: [ │
│ { name: "手机", price: 5000 } │
│ ], │
│ total: 5000 │
│ } │
│ ] │
│ } │
│ │
│ 一张表 + 内嵌文档 + 数组 │
│ 优点:灵活扩展、一次查询获取完整对象 │
│ 缺点:无 JOIN、需要反规范化、事务支持较弱 │
│ │
└─────────────────────────────────────────────────────────────┘1.2 MongoDB 的 JSON 文档
javascript
// MongoDB 文档(BSON 格式,类 JSON 的二进制格式)
{
"_id": ObjectId("507f1f77bcf86cd799439011"),
"username": "zhangsan",
"profile": {
"age": 28,
"email": "zhang@example.com",
"tags": ["程序员", "猫奴", "羽毛球"], // 数组
"address": {
"city": "北京",
"district": "海淀区"
}
},
"orders": [
{
"orderId": "ORD20260508001",
"items": [
{ "name": "键盘", "qty": 1, "price": 299 },
{ "name": "鼠标", "qty": 2, "price": 89 }
],
"total": 477,
"createdAt": ISODate("2026-05-08T10:30:00Z")
}
],
"status": "active",
"createdAt": ISODate("2025-01-01T00:00:00Z")
}第2部分:核心操作
2.1 CRUD 操作
javascript
// 连接 MongoDB
const { MongoClient } = require('mongodb');
const client = new MongoClient('mongodb://localhost:27017');
const db = client.db('myapp');
// ================== 插入 ==================
// 单条插入
await db.collection('users').insertOne({
name: '张三',
age: 28,
tags: ['程序员', '羽毛球'],
createdAt: new Date()
});
// 批量插入
await db.collection('products').insertMany([
{ name: '键盘', price: 299, stock: 100, category: '外设' },
{ name: '鼠标', price: 89, stock: 200, category: '外设' },
{ name: '显示器', price: 1999, stock: 50, category: '配件' }
]);
// ================== 查询 ==================
// 基本查询
const user = await db.collection('users').findOne({ name: '张三' });
// 条件查询
const products = await db.collection('products').find({
price: { $gt: 100, $lt: 1000 }, // 100 < price < 1000
category: '外设',
stock: { $gte: 10 } // stock >= 10
}).toArray();
// 分页查询
const page3 = await db.collection('products')
.find({ category: '外设' })
.sort({ price: 1 }) // 按价格升序
.skip(20) // 跳过前 20 条
.limit(10) // 取 10 条
.toArray();
// 投影(只返回特定字段)
const names = await db.collection('products')
.find({}, { projection: { name: 1, price: 1 } }) // 只返回 name 和 price
.toArray();
// ================== 更新 ==================
// 原子更新
await db.collection('users').updateOne(
{ name: '张三' },
{
$inc: { age: 1 }, // age 自增 1
$set: { updatedAt: new Date() },
$push: { tags: '新手' } // 数组追加
}
);
// 替换整个文档
await db.collection('users').replaceOne(
{ name: '张三' },
{ name: '张三', age: 29, tags: ['程序员'] }
);
// ================== 删除 ==================
await db.collection('users').deleteOne({ name: '张三' });
await db.collection('products').deleteMany({ stock: 0 }); // 删除库存为 0 的2.2 常用查询操作符
javascript
// 比较操作符
{ price: { $eq: 100 } } // = 100(简写:{price: 100})
{ price: { $ne: 100 } } // != 100
{ price: { $gt: 100 } } // > 100
{ price: { $gte: 100 } } // >= 100
{ price: { $lt: 100 } } // < 100
{ price: { $lte: 100 } } // <= 100
{ price: { $in: [99, 199, 299] } } // IN [99, 199, 299]
{ price: { $nin: [99, 199] } } // NOT IN
// 逻辑操作符
{ $and: [{ price: { $gt: 100 } }, { stock: { $gt: 0 } }] }
{ $or: [{ price: { $lt: 100 } }, { name: '键盘' }] }
{ $not: { price: { $gt: 1000 } } }
// 数组操作符
{ tags: '程序员' } // 数组包含"程序员"
{ tags: { $all: ['程序员', '羽毛球'] } } // 同时包含两者
{ tags: { $size: 2 } } // 数组长度为 2
{ 'profile.address.city': '北京' } // 嵌套字段
// 字符串操作符
{ name: { $regex: '^键', $options: 'i' } } // 正则匹配(i=忽略大小写)
{ name: { $regex: '键盘|鼠标' } } // 或匹配
// 存在性
{ email: { $exists: true, $ne: null } } // 存在且非 null第3部分:Aggregation Pipeline
聚合管道是 MongoDB 最强大的功能:
javascript
// 聚合管道:将多个处理阶段串联起来
// 等价于 SQL: SELECT category, COUNT(*), AVG(price)
// FROM products GROUP BY category
const stats = await db.collection('products').aggregate([
// Stage 1: 过滤(相当于 WHERE)
{ $match: { stock: { $gt: 0 } } },
// Stage 2: 分组(相当于 GROUP BY)
{ $group: {
_id: '$category',
count: { $sum: 1 },
avgPrice: { $avg: '$price' },
totalStock: { $sum: '$stock' },
cheapest: { $min: '$price' },
mostExpensive: { $max: '$price' }
}},
// Stage 3: 排序(相当于 ORDER BY)
{ $sort: { totalStock: -1 } },
// Stage 4: 投影(相当于 SELECT)
{ $project: {
_id: 0, // 隐藏 _id
category: '$_id', // 重命名
count: 1,
avgPrice: { $round: ['$avgPrice', 2] }, // 保留 2 位小数
totalStock: 1
}}
]).toArray();
console.log(stats);
// [ { category: '外设', count: 2, avgPrice: 194, totalStock: 300 }, ... ]
// 实战:统计用户月订单额
const monthlyRevenue = await db.collection('orders').aggregate([
{ $match: { status: 'completed', createdAt: { $gte: new Date('2026-01-01') } } },
// 展开订单商品数组
{ $unwind: '$items' },
// 计算每件商品小计
{ $addFields: {
itemTotal: { $multiply: ['$items.qty', '$items.price'] }
}},
// 按月分组
{ $group: {
_id: {
year: { $year: '$createdAt' },
month: { $month: '$createdAt' }
},
revenue: { $sum: '$itemTotal' },
orderCount: { $sum: 1 }
}},
{ $sort: { '_id.year': 1, '_id.month': 1 } }
]).toArray();
// 实战:用户购买力分层
const userTiers = await db.collection('orders').aggregate([
{ $group: {
_id: '$userId',
totalSpent: { $sum: '$total' }
}},
{ $bucket: {
groupBy: '$totalSpent',
boundaries: [0, 1000, 5000, 10000, Infinity],
default: '其他',
output: {
count: { $sum: 1 },
users: { $push: '$_id' }
}
}}
]).toArray();第4部分:索引
javascript
// 创建索引
await db.collection('users').createIndex({ email: 1 }, { unique: true }); // 唯一索引
await db.collection('products').createIndex({ price: 1, stock: -1 }); // 复合索引
await db.collection('products').createIndex({ name: 'text' }); // 文本索引
await db.collection('users').createIndex({ 'profile.address.city': 1 } ); // 嵌套字段索引
// 地理空间索引
await db.collection('stores').createIndex({ location: '2dsphere' });
await db.collection('stores').find({
location: {
$nearSphere: {
$geometry: { type: 'Point', coordinates: [116.45, 39.93] },
$maxDistance: 5000 // 5 公里
}
}
}).toArray();
// 查看查询计划(相当于 EXPLAIN)
const explain = await db.collection('products')
.find({ price: { $gt: 100 }, category: '外设' })
.explain('executionStats');
console.log(explain.executionStats.totalDocsExamined, explain.executionStats.nReturned);
// 列出所有索引
await db.collection('users').indexes();第5部分:副本集与分片
5.1 副本集(Replica Set)
┌─────────────────────────────────────────────────────────────┐
│ MongoDB 副本集架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Primary (主节点) │
│ 接受所有写操作 │
│ │ ▲ │
│ 同步 ── │ ── 复制 │
│ ▼ │ │
│ ┌───────────┴───────────┐ │
│ │ │ │
│ Secondary Secondary │
│ (从节点1) (从节点2) │
│ 可读/不可写 可读/不可写 │
│ │
│ 副本集选举: │
│ • Primary 宕机 → 剩余节点自动投票 → 新 Primary │
│ • 默认 3 节点(1 Primary + 2 Secondary) │
│ • 建议奇数节点(避免脑裂) │
│ │
│ 读写分离: │
│ • 写 → 只能 Primary │
│ • 读 → 可以配置 readPreference │
│ - primary(默认) │
│ - primaryPreferred │
│ - secondary(读负载均衡,但可能有延迟) │
│ - nearest(就近节点) │
│ │
└─────────────────────────────────────────────────────────────┘5.2 分片集群(Sharding)
┌─────────────────────────────────────────────────────────────┐
│ MongoDB 分片集群架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ mongos(路由节点) │ │
│ │ 客户端连接 mongos,自动路由到分片 │ │
│ └──────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌────────────┼────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Shard 1 │ │ Shard 2 │ │ Shard 3 │ │
│ │ (chunks │ │ (chunks │ │ (chunks │ │
│ │ [0,25)) │ │ [25,50)) │ │ [50,max])│ │
│ └─────┬────┘ └─────┬────┘ └─────┬────┘ │
│ │ │ │ │
│ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │
│ │RS成员1 │ │RS成员1 │ │RS成员1 │ │
│ │RS成员2 │ │RS成员2 │ │RS成员2 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ config server(配置服务器):存储元数据(分片信息) │
│ │
│ 分片键选择原则: │
│ • 高基数(不同值多) │
│ • 非单调递增(避免热点) │
│ • 常用于查询 │
│ 例:user_id(高基数)、created_at(非单调) │
│ 避免:ObjectId(单调递增)、timestamp(单调递增) │
│ │
└─────────────────────────────────────────────────────────────┘第6部分:mongoose ODM
javascript
// mongoose: MongoDB 的对象模型工具
const mongoose = require('mongoose');
// 定义 Schema
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
index: true
},
email: {
type: String,
required: true,
unique: true,
lowercase: true
},
age: {
type: Number,
min: 0,
max: 150
},
tags: [String], // 字符串数组
orders: [{
orderId: String,
total: Number,
items: [{
name: String,
qty: Number,
price: Number
}]
}],
profile: {
bio: String,
website: String
}
}, {
timestamps: true // 自动添加 createdAt 和 updatedAt
});
// 添加实例方法
userSchema.methods.getFullName = function() {
return `${this.name} (${this.email})`;
};
// 添加静态方法
userSchema.statics.findByEmail = function(email) {
return this.findOne({ email: email.toLowerCase() });
};
// 添加中间件(钩子)
userSchema.pre('save', function(next) {
console.log(`Saving user: ${this.name}`);
next();
});
// 创建 Model
const User = mongoose.model('User', userSchema);
// 实战:关联查询
const Order = mongoose.model('Order', new mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
items: [{ name: String, price: Number }],
total: Number
}));
// populate(填充关联)
const orders = await Order.find()
.populate('user', 'name email')
.sort({ createdAt: -1 });第7部分:MongoDB 的适用与不适用场景
┌─────────────────────────────────────────────────────────────┐
│ MongoDB 适用 vs 不适用场景 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 适用场景: │
│ ✅ 内容管理系统(CMS)、博客、论坛 │
│ ✅ 用户生成内容(UGC)—— 帖子、评论、动态 │
│ ✅ 实时分析/事件日志—— 结构随时变化 │
│ ✅ 产品目录—— 商品属性差异大(服装 vs 电子产品) │
│ ✅ 物联网(IoT)—— 传感器数据,字段不固定 │
│ ✅ 快速原型开发—— Schema 变化频繁 │
│ │
│ 不适用场景: │
│ ❌ 强事务场景—— 银行转账(用 PostgreSQL / TiDB) │
│ ❌ 强关联场景—— 多表 JOIN(用 MySQL / PostgreSQL) │
│ ❌ 复杂分析—— 跨文档聚合(用 ClickHouse / PostgreSQL) │
│ ❌ 需要 SQL 的场景—— 团队只会 SQL │
│ ❌ 小文件存储—— GridFS 性能不如对象存储 │
│ │
│ 与 Agent/LLM 的关系: │
│ • MongoDB 在 AI 应用中常用于:消息日志、用户行为数据 │
│ • 但核心业务数据(用户配置、Agent 定义)用 PostgreSQL │
│ • 灵活 Schema 让 MongoDB 适合存储多变的 Agent 会话数据 │
│ │
└─────────────────────────────────────────────────────────────┘学习状态:🟡 开始学习