← 返回首页

核心特性

📄
文档模型

灵活的 JSON 文档结构,无需预定义表结构,支持嵌套文档和数组

🔍
强大查询

支持复杂查询、聚合管道、全文搜索、地理空间查询

📈
水平扩展

内置分片支持,轻松应对 PB 级海量数据存储

高性能

内存映射文件、索引优化、读写分离,毫秒级响应

🔄
高可用

副本集自动故障转移,数据多副本存储,99.99% 可用性

🔐
ACID 事务

支持多文档 ACID 事务,保证数据一致性

MongoDB vs 关系型数据库

特性 MongoDB MySQL
数据模型 文档(JSON/BSON) 表(行和列)
Schema 灵活,可动态修改 固定,需要迁移
关联查询 嵌套文档 / $lookup JOIN
扩展方式 水平扩展(分片) 垂直扩展为主
适用场景 高并发、大数据、敏捷开发 复杂事务、强一致性

Schema 设计

完整的 Schema 定义

使用 Mongoose Schema 定义文档结构、数据类型、验证规则、默认值、虚拟属性和中间件。

models/User.js
const mongoose = require('mongoose'); const bcrypt = require('bcryptjs'); const userSchema = new mongoose.Schema({ // 基本字段 username: { type: String, required: [true, '用户名必填'], unique: true, trim: true, minlength: [2, '用户名至少2个字符'], maxlength: [20, '用户名最多20个字符'] }, email: { type: String, required: [true, '邮箱必填'], unique: true, lowercase: true, match: [/^\S+@\S+\.\S+$/, '邮箱格式不正确'] }, password: { type: String, required: [true, '密码必填'], minlength: [6, '密码至少6个字符'], select: false // 默认不返回密码字段 }, // 枚举类型 role: { type: String, enum: ['user', 'admin', 'moderator'], default: 'user' }, // 嵌套对象 profile: { avatar: { type: String, default: '/images/default-avatar.png' }, bio: { type: String, maxlength: [200, '简介最多200字'] }, website: String, location: String }, // 数组字段 tags: [{ type: String, trim: true }], // 引用关联(一对多) posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }], // 嵌套数组对象 socialLinks: [{ platform: { type: String, enum: ['github', 'twitter', 'linkedin'] }, url: String }], // 状态字段 isActive: { type: Boolean, default: true }, isVerified: { type: Boolean, default: false }, lastLoginAt: Date, // 软删除 deletedAt: { type: Date, default: null } }, { timestamps: true, // 自动添加 createdAt, updatedAt toJSON: { virtuals: true }, toObject: { virtuals: true } }); // 虚拟属性(不存储在数据库) userSchema.virtual('fullName').get(function() { return `${this.profile.firstName} ${this.profile.lastName}`; }); // 索引 userSchema.index({ email: 1 }); userSchema.index({ username: 'text', 'profile.bio': 'text' }); userSchema.index({ createdAt: -1 }); userSchema.index({ isActive: 1, role: 1 }); // 保存前中间件(哈希密码) userSchema.pre('save', async function(next) { if (!this.isModified('password')) return next(); this.password = await bcrypt.hash(this.password, 12); next(); }); // 实例方法 userSchema.methods.comparePassword = async function(candidatePassword) { return await bcrypt.compare(candidatePassword, this.password); }; // 静态方法 userSchema.statics.findByEmail = function(email) { return this.findOne({ email: email.toLowerCase() }); }; // 查询中间件(自动过滤软删除) userSchema.pre(/^find/, function(next) { this.find({ deletedAt: null }); next(); }); module.exports = mongoose.model('User', userSchema);

Schema 设计原则

  • 嵌入 vs 引用 - 频繁一起查询的数据用嵌入,独立访问的数据用引用
  • 避免无限增长的数组 - 数组元素超过几百个时考虑拆分
  • 反范式化 - 适当冗余数据以减少关联查询
  • 预计算 - 对于聚合统计,可以存储预计算结果
💡 设计建议

MongoDB 的设计理念是"为查询而设计"。先确定你的查询模式,再设计 Schema 结构。

CRUD 操作

创建文档

services/userService.js - Create
const User = require('../models/User'); // 创建单个文档 const user = await User.create({ username: 'zhangsan', email: 'zhangsan@example.com', password: '123456', profile: { bio: '全栈开发者' } }); // 或使用 new + save const user = new User({ username: 'lisi' }); await user.save(); // 批量创建 const users = await User.insertMany([ { username: 'user1', email: 'user1@test.com' }, { username: 'user2', email: 'user2@test.com' } ], { ordered: false }); // ordered: false 允许部分失败

查询文档

services/userService.js - Read
// 查询单个 const user = await User.findById(id); const user = await User.findOne({ email: 'test@example.com' }); // 查询多个(带条件) const users = await User.find({ isActive: true, role: { $in: ['user', 'admin'] }, createdAt: { $gte: new Date('2024-01-01') } }); // 链式查询 const users = await User.find({ isActive: true }) .select('username email profile.avatar') // 选择字段 .sort({ createdAt: -1 }) // 排序 .skip(20) // 跳过 .limit(10) // 限制 .populate('posts', 'title createdAt') // 关联查询 .lean(); // 返回纯 JS 对象 // 查询并计数 const [users, total] = await Promise.all([ User.find(query).skip(skip).limit(limit), User.countDocuments(query) ]); // 检查是否存在 const exists = await User.exists({ email: 'test@example.com' }); // 去重查询 const roles = await User.distinct('role');

更新文档

services/userService.js - Update
// 更新单个(返回更新后的文档) const user = await User.findByIdAndUpdate( id, { $set: { 'profile.bio': '新简介' }, $push: { tags: 'nodejs' } }, { new: true, // 返回更新后的文档 runValidators: true // 运行验证器 } ); // 更新操作符 await User.updateOne( { _id: id }, { $set: { name: '新名字' }, // 设置字段 $unset: { tempField: 1 }, // 删除字段 $inc: { viewCount: 1 }, // 自增 $push: { tags: 'new' }, // 数组添加 $pull: { tags: 'old' }, // 数组移除 $addToSet: { tags: 'unique' } // 数组去重添加 } ); // 批量更新 const result = await User.updateMany( { isActive: false }, { $set: { deletedAt: new Date() } } ); console.log(`更新了 ${result.modifiedCount} 条记录`); // 查找并更新(原子操作) const user = await User.findOneAndUpdate( { email: 'test@example.com' }, { $inc: { loginCount: 1 } }, { upsert: true, new: true } // 不存在则创建 );

删除文档

services/userService.js - Delete
// 删除单个 await User.findByIdAndDelete(id); await User.deleteOne({ _id: id }); // 批量删除 const result = await User.deleteMany({ isActive: false, createdAt: { $lt: new Date('2023-01-01') } }); // 软删除(推荐) await User.findByIdAndUpdate(id, { deletedAt: new Date(), isActive: false });

高级查询

查询操作符

操作符 说明 示例
$eq / $ne 等于 / 不等于 { age: { $eq: 18 } }
$gt / $gte 大于 / 大于等于 { age: { $gte: 18 } }
$lt / $lte 小于 / 小于等于 { age: { $lt: 60 } }
$in / $nin 在数组中 / 不在数组中 { role: { $in: ['admin', 'mod'] } }
$regex 正则匹配 { name: { $regex: /^张/, $options: 'i' } }
$exists 字段是否存在 { avatar: { $exists: true } }
$and / $or 逻辑与 / 或 { $or: [{ a: 1 }, { b: 2 }] }

复杂查询示例

复杂查询
// 多条件组合查询 const users = await User.find({ $and: [ { isActive: true }, { $or: [ { role: 'admin' }, { 'profile.level': { $gte: 5 } } ] }, { tags: { $in: ['vip', 'premium'] } } ] }); // 数组查询 const users = await User.find({ tags: { $all: ['nodejs', 'mongodb'] }, // 包含所有 'socialLinks.platform': 'github' // 嵌套数组查询 }); // 全文搜索(需要文本索引) const results = await User.find( { $text: { $search: 'nodejs developer' } }, { score: { $meta: 'textScore' } } ).sort({ score: { $meta: 'textScore' } }); // 地理空间查询(需要 2dsphere 索引) const nearbyUsers = await User.find({ location: { $near: { $geometry: { type: 'Point', coordinates: [116.4, 39.9] }, $maxDistance: 5000 // 5公里内 } } });

聚合管道

聚合管道基础

聚合管道是 MongoDB 最强大的数据处理功能,可以进行复杂的数据转换、分组、统计等操作。

聚合查询示例
// 用户统计分析 const stats = await User.aggregate([ // 1. 过滤 { $match: { isActive: true } }, // 2. 关联查询 { $lookup: { from: 'posts', localField: '_id', foreignField: 'author', as: 'userPosts' }}, // 3. 添加计算字段 { $addFields: { postCount: { $size: '$userPosts' }, accountAge: { $divide: [ { $subtract: [new Date(), '$createdAt'] }, 86400000 // 毫秒转天 ] } }}, // 4. 分组统计 { $group: { _id: '$role', totalUsers: { $sum: 1 }, avgPosts: { $avg: '$postCount' }, maxPosts: { $max: '$postCount' }, users: { $push: '$username' } }}, // 5. 排序 { $sort: { totalUsers: -1 } }, // 6. 重塑输出 { $project: { role: '$_id', totalUsers: 1, avgPosts: { $round: ['$avgPosts', 2] }, _id: 0 }} ]); // 按时间分组统计(日活) const dailyStats = await User.aggregate([ { $match: { lastLoginAt: { $gte: new Date('2024-01-01') } }}, { $group: { _id: { year: { $year: '$lastLoginAt' }, month: { $month: '$lastLoginAt' }, day: { $dayOfMonth: '$lastLoginAt' } }, activeUsers: { $sum: 1 } }}, { $sort: { '_id.year': 1, '_id.month': 1, '_id.day': 1 } } ]);

常用聚合阶段

阶段 说明
$match 过滤文档,类似 find() 的查询条件
$group 分组并计算聚合值(sum, avg, max, min 等)
$project 选择、重命名、计算字段
$lookup 左外连接,关联其他集合
$unwind 展开数组,每个元素生成一个文档
$sort 排序
$limit / $skip 分页
$facet 多管道并行处理

索引优化

索引类型与创建

索引定义
// 单字段索引 userSchema.index({ email: 1 }); // 1: 升序, -1: 降序 // 复合索引(查询顺序很重要) userSchema.index({ isActive: 1, createdAt: -1 }); // 唯一索引 userSchema.index({ username: 1 }, { unique: true }); // 稀疏索引(只索引存在该字段的文档) userSchema.index({ phone: 1 }, { sparse: true }); // TTL 索引(自动过期删除) sessionSchema.index({ createdAt: 1 }, { expireAfterSeconds: 3600 }); // 文本索引(全文搜索) postSchema.index({ title: 'text', content: 'text' }); // 地理空间索引 locationSchema.index({ coordinates: '2dsphere' });

查询性能分析

explain() 分析
// 分析查询执行计划 const explanation = await User.find({ email: 'test@example.com' }) .explain('executionStats'); console.log('查询计划:', explanation.queryPlanner.winningPlan); console.log('扫描文档数:', explanation.executionStats.totalDocsExamined); console.log('返回文档数:', explanation.executionStats.nReturned); console.log('执行时间:', explanation.executionStats.executionTimeMillis); // 理想情况:totalDocsExamined ≈ nReturned // 如果 totalDocsExamined >> nReturned,说明需要优化索引
⚠️ 索引注意事项

索引会占用磁盘空间并降低写入性能。只为常用查询创建索引,避免过度索引。复合索引遵循最左前缀原则。

事务处理

多文档事务

MongoDB 4.0+ 支持多文档 ACID 事务,确保跨集合操作的原子性。

事务示例
const mongoose = require('mongoose'); async function transferCredits(fromUserId, toUserId, amount) { const session = await mongoose.startSession(); try { session.startTransaction(); // 扣除发送方积分 const sender = await User.findByIdAndUpdate( fromUserId, { $inc: { credits: -amount } }, { session, new: true } ); if (sender.credits < 0) { throw new Error('积分不足'); } // 增加接收方积分 await User.findByIdAndUpdate( toUserId, { $inc: { credits: amount } }, { session } ); // 记录交易 await Transaction.create([{ from: fromUserId, to: toUserId, amount, type: 'transfer' }], { session }); // 提交事务 await session.commitTransaction(); return { success: true }; } catch (error) { // 回滚事务 await session.abortTransaction(); throw error; } finally { session.endSession(); } }

连接管理

连接配置最佳实践

config/database.js
const mongoose = require('mongoose'); const connectDB = async () => { try { const conn = await mongoose.connect(process.env.MONGODB_URI, { // 连接池配置 maxPoolSize: 10, // 最大连接数 minPoolSize: 2, // 最小连接数 // 超时配置 serverSelectionTimeoutMS: 5000, // 服务器选择超时 socketTimeoutMS: 45000, // Socket 超时 connectTimeoutMS: 10000, // 连接超时 // 重试配置 retryWrites: true, retryReads: true, // 写入确认 w: 'majority', // 读取偏好 readPreference: 'primaryPreferred' }); console.log(`MongoDB 已连接: ${conn.connection.host}`); } catch (error) { console.error('MongoDB 连接失败:', error.message); process.exit(1); } }; // 监听连接事件 mongoose.connection.on('connected', () => console.log('Mongoose 已连接')); mongoose.connection.on('error', (err) => console.error('Mongoose 错误:', err)); mongoose.connection.on('disconnected', () => console.log('Mongoose 已断开')); // 优雅关闭 process.on('SIGINT', async () => { await mongoose.connection.close(); console.log('MongoDB 连接已关闭'); process.exit(0); }); module.exports = connectDB;

环境变量配置

.env
# 本地开发 MONGODB_URI=mongodb://localhost:27017/myapp # Docker 环境 MONGODB_URI=mongodb://admin:password@mongo:27017/myapp?authSource=admin # 副本集 MONGODB_URI=mongodb://host1:27017,host2:27017,host3:27017/myapp?replicaSet=rs0 # MongoDB Atlas 云数据库 MONGODB_URI=mongodb+srv://user:pass@cluster.mongodb.net/myapp?retryWrites=true&w=majority