MongoDB 入门详解
前言
在构建现代 Web 应用时,我们经常需要在关系型数据库(MySQL、PostgreSQL)和文档型数据库(MongoDB)之间做选择。MongoDB 以其灵活的文档模型、强大的查询能力和水平扩展特性,成为许多开发者的首选。本篇文章将带你从零开始掌握 MongoDB 的核心用法。
一、MongoDB 基础概念
1.1 解决了什么问题?
- 关系型数据库需要预定义表结构,字段变更成本高。
- 某些业务场景(如用户画像、内容管理)需要灵活的数据结构,不同文档可能有不同字段。
- MongoDB 采用 BSON(Binary JSON) 格式存储文档,无需固定 schema,支持嵌套对象和数组。
1.2 核心概念对比
| 概念 | 关系型数据库 | MongoDB |
|---|---|---|
| 数据库 | Database | Database |
| 表 | Table | Collection(集合) |
| 行 | Row | Document(文档) |
| 列 | Column | Field(字段) |
| 主键 | Primary Key | _id(自动生成) |
- Collection(集合):类似于表,但无需预定义结构。
- Document(文档):类似于行,但可以包含嵌套对象和数组。
- Field(字段):文档中的键值对,支持多种数据类型。
二、安装与连接
2.1 本地安装
macOS(使用 Homebrew):
brew tap mongodb/brew
brew install mongodb-community
brew services start mongodb-communityDocker 方式(推荐):
docker run -d -p 27017:27017 --name mongodb mongo:latest2.2 连接字符串格式
mongodb://[username:password@]host[:port][/database][?options]示例:
mongodb://localhost:27017/myapp
mongodb://user:pass@localhost:27017/myapp?authSource=admin
mongodb+srv://user:pass@cluster.mongodb.net/myapp- 默认端口:
27017 - 无认证时可直接连接本地实例
- MongoDB Atlas(云服务)使用
mongodb+srv://协议
三、Node.js 连接实战
使用 mongoose 库(最流行的 MongoDB ODM):
import mongoose from 'mongoose';
// 连接 MongoDB
const connectDB = async () => {
try {
await mongoose.connect('mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('MongoDB 连接成功');
} catch (error) {
console.error('连接失败:', error);
process.exit(1);
}
};
connectDB();
// 定义 Schema(数据模型)
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, unique: true, required: true },
age: { type: Number, min: 0 },
tags: [String],
createdAt: { type: Date, default: Date.now },
});
// 创建 Model
const User = mongoose.model('User', userSchema);mongoose.connect()返回 Promise,支持 async/await- Schema 定义字段类型和验证规则
- Model 提供数据库操作方法
四、CRUD 操作详解
4.1 创建(Create)
// 单条插入
const newUser = await User.create({
name: '张三',
email: 'zhangsan@example.com',
age: 25,
tags: ['前端', 'Node.js'],
});
// 或使用 save()
const user = new User({ name: '李四', email: 'lisi@example.com' });
await user.save();
// 批量插入
await User.insertMany([
{ name: '王五', email: 'wangwu@example.com' },
{ name: '赵六', email: 'zhaoliu@example.com' },
]);4.2 查询(Read)
// 查询所有
const users = await User.find();
// 条件查询
const user = await User.findOne({ email: 'zhangsan@example.com' });
const adults = await User.find({ age: { $gte: 18 } });
// 投影(只返回指定字段)
const names = await User.find({}, 'name email');
// 排序与分页
const users = await User.find()
.sort({ createdAt: -1 })
.skip(10)
.limit(20);
// 复杂查询
const result = await User.find({
age: { $gte: 18, $lte: 65 },
tags: { $in: ['前端', '后端'] },
name: { $regex: /张/, $options: 'i' },
});常用查询操作符:
$gt、$gte、$lt、$lte:比较$in、$nin:包含/不包含$regex:正则匹配$exists:字段存在性$or、$and:逻辑运算
4.3 更新(Update)
// 更新单条(返回更新后的文档)
const updated = await User.findOneAndUpdate(
{ email: 'zhangsan@example.com' },
{ age: 26 },
{ new: true } // 返回更新后的文档
);
// 更新多条
await User.updateMany(
{ age: { $lt: 18 } },
{ $set: { status: 'minor' } }
);
// 使用操作符
await User.updateOne(
{ _id: userId },
{
$inc: { age: 1 }, // 自增
$push: { tags: '新标签' }, // 数组追加
$set: { updatedAt: Date.now() }
}
);常用更新操作符:
$set:设置字段值$unset:删除字段$inc:数值增减$push、$pull:数组操作$addToSet:数组去重追加
4.4 删除(Delete)
// 删除单条
await User.deleteOne({ email: 'zhangsan@example.com' });
// 删除多条
await User.deleteMany({ age: { $lt: 18 } });
// 查找并删除(返回被删除的文档)
const deleted = await User.findOneAndDelete({ _id: userId });五、聚合查询(Aggregation)
聚合管道是 MongoDB 最强大的功能之一,类似 SQL 的 GROUP BY:
// 统计各年龄段用户数量
const stats = await User.aggregate([
{ $match: { age: { $exists: true } } }, // 过滤
{ $group: {
_id: { $switch: {
branches: [
{ case: { $lt: ['$age', 18] }, then: '未成年' },
{ case: { $lt: ['$age', 30] }, then: '青年' },
{ case: { $lt: ['$age', 50] }, then: '中年' },
{ case: true, then: '老年' }
],
default: '未知'
}},
count: { $sum: 1 },
avgAge: { $avg: '$age' }
}},
{ $sort: { count: -1 } }
]);
// 查找包含特定标签的用户,并展开标签数组
const result = await User.aggregate([
{ $match: { tags: { $exists: true, $ne: [] } } },
{ $unwind: '$tags' },
{ $group: {
_id: '$tags',
users: { $push: '$name' }
}}
]);常用聚合阶段:
$match:过滤文档$group:分组统计$project:字段投影$sort:排序$limit、$skip:分页$unwind:展开数组$lookup:关联查询(类似 JOIN)
六、索引优化
索引能大幅提升查询性能:
// 创建单字段索引
await User.createIndex({ email: 1 }); // 1 升序,-1 降序
// 创建复合索引
await User.createIndex({ name: 1, age: -1 });
// 创建文本索引(支持全文搜索)
await User.createIndex({ name: 'text', bio: 'text' });
// 查看索引
const indexes = await User.collection.getIndexes();
// 删除索引
await User.collection.dropIndex('email_1');索引策略建议:
- 为频繁查询的字段创建索引
- 为唯一字段(如 email)创建唯一索引
- 复合索引遵循"最左前缀"原则
- 避免过多索引,影响写入性能
- 使用
explain()分析查询计划
// 分析查询性能
const explain = await User.find({ email: 'test@example.com' }).explain();
console.log(explain.executionStats);七、实战示例:博客文章系统
结合《博客搭建二-文章接口》,用 MongoDB 实现文章管理:
import mongoose from 'mongoose';
// 文章 Schema
const articleSchema = new mongoose.Schema({
title: { type: String, required: true, index: true },
content: { type: String, required: true },
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
tags: [String],
views: { type: Number, default: 0 },
published: { type: Boolean, default: false },
publishedAt: Date,
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now },
});
// 自动更新 updatedAt
articleSchema.pre('save', function(next) {
this.updatedAt = Date.now();
next();
});
const Article = mongoose.model('Article', articleSchema);
// 创建文章
const createArticle = async (data) => {
return await Article.create({
...data,
publishedAt: data.published ? Date.now() : null,
});
};
// 查询已发布文章(带作者信息)
const getPublishedArticles = async (page = 1, limit = 10) => {
return await Article.find({ published: true })
.populate('author', 'name email') // 关联查询用户信息
.sort({ publishedAt: -1 })
.skip((page - 1) * limit)
.limit(limit);
};
// 增加浏览量
const incrementViews = async (articleId) => {
return await Article.findByIdAndUpdate(
articleId,
{ $inc: { views: 1 } },
{ new: true }
);
};
// 按标签统计文章数
const getTagStats = async () => {
return await Article.aggregate([
{ $match: { published: true } },
{ $unwind: '$tags' },
{ $group: {
_id: '$tags',
count: { $sum: 1 },
articles: { $push: { title: '$title', id: '$_id' } }
}},
{ $sort: { count: -1 } }
]);
};八、性能优化与最佳实践
连接池管理
mongoose.connect(uri, { maxPoolSize: 10, // 最大连接数 minPoolSize: 5, // 最小连接数 serverSelectionTimeoutMS: 5000, });批量操作优化
// 使用 bulkWrite 替代多次 updateOne await User.bulkWrite([ { updateOne: { filter: { _id: id1 }, update: { $set: { status: 'active' } } } }, { updateOne: { filter: { _id: id2 }, update: { $set: { status: 'inactive' } } } }, ]);查询优化
- 使用
select()只查询需要的字段 - 避免
$regex在大量数据上的全表扫描 - 合理使用
lean()返回纯 JavaScript 对象(不创建 Mongoose 文档实例)
const users = await User.find().lean(); // 性能更好,但失去 Mongoose 方法- 数据验证
const userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true, validate: { validator: (v) => /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(v), message: '邮箱格式不正确' } } });
九、常见排障清单
MongoServerError: E11000 duplicate key error- 检查唯一索引字段是否有重复值,或删除冲突的索引后重建。
MongooseError: Operationusers.findOne()buffering timed out after 10000ms- 确认 MongoDB 服务已启动,检查连接字符串是否正确。
查询性能慢
- 使用
explain()分析查询计划,检查是否使用了索引。 - 考虑添加复合索引或优化查询条件。
- 使用
CastError: Cast to ObjectId failed- 确保传入的
_id是有效的 MongoDB ObjectId 格式(24 位十六进制字符串)。
- 确保传入的
内存占用过高
- 使用
lean()查询,避免创建大量 Mongoose 文档实例。 - 限制查询结果数量,使用分页。
- 使用
总结
- MongoDB 是文档型数据库,适合灵活的数据结构和快速迭代。
- Schema 定义提供数据验证,但 MongoDB 本身支持无结构存储。
- 索引是性能优化的关键,合理使用单字段、复合和文本索引。
- 聚合管道功能强大,能处理复杂的统计和分析需求。
- 在 Node.js 中使用
mongoose可以简化操作,但要注意连接池和查询优化。 - 生产环境建议使用 MongoDB Atlas 或配置副本集,保证高可用性。
掌握 MongoDB 的核心用法和优化技巧,你就能在项目中灵活选择数据存储方案,构建高性能、可扩展的 Web 应用。
原文地址:https://webfem.com/post/mongodb-usage-guide,转载请注明出处