MongoDB
目录
基础概念
什么是 MongoDB
- NoSQL 数据库:存储键值对数据
- BSON 格式:Binary JSON,类似 JSON 的二进制存储格式
- 文档数据库:数据以文档形式存储,类似 JSON 对象
术语对比
关系数据库 | MongoDB | 说明 |
---|---|---|
database | database | 数据库 |
table | collection | 集合 |
row | document | 文档 |
column | field | 字段 |
primary key | _id | 主键(自动生成) |
数据示例
javascript
{
"_id" : ObjectId("5c89f787ca6e4e3ac1ecabkk"),
"_plat" : "test_plat0",
"update_time" : ISODate("2019-06-03T15:00:42.142Z"),
"create_time" : ISODate("2019-03-14T14:41:11.217Z"),
"creator" : "test_user",
"admin" : [
"admin1",
"admin2"
],
"ops" : [
"ops1"
],
"labels" : {
"department" : "departmentA",
"main_class" : "mainClassA"
}
}
数据格式规则
- 键不能含有
\0
(空字符)。这个字符用来表示键的结尾。 .
和$
有特别的意义,只有在特定环境下才能使用。- 以下划线
_
开头的键是保留的(不是严格要求的)。
安装配置
快速安装
Windows
- 下载 MongoDB Community Server
- 运行安装程序
- 安装 MongoDB Compass(图形化管理工具)
Linux (Ubuntu/Debian)
bash
wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
macOS
bash
brew tap mongodb/brew
brew install mongodb-community
启动服务
bash
# 启动服务
sudo systemctl start mongod
# 设置开机自启
sudo systemctl enable mongod
# 检查状态
sudo systemctl status mongod
连接数据库
bash
# 本地连接
mongo
# 指定主机端口
mongo --host localhost --port 27017
# 连接指定数据库
mongo mydb
基础操作
数据库操作
javascript
// 显示所有数据库
show dbs
// 切换/创建数据库
use mydb
// 显示当前数据库
db
// 删除当前数据库
db.dropDatabase()
集合操作
javascript
// 显示所有集合
show collections
// 创建集合
db.createCollection("users")
// 创建带选项的集合
db.createCollection("logs", {
capped: true, // 固定大小集合
size: 100000, // 最大字节数
max: 1000 // 最大文档数
})
// 删除集合
db.users.drop()
数据插入
插入数据有4种方法:insert
, insertOne
, insertMany
, save
insert
insert
可以插入一条或多条数据
javascript
// 插入一条数据
db.col.insert({"name":"xiaoming"})
// 插入多条数据
db.col.insert([{"name":"xiaoming"}, {"name":"test_user"}])
insertOne
insertOne
只能插入一条数据
javascript
// 一条数据
db.col.insertOne({"name":"xiaoming"})
insertMany
insertMany
可以插入一条或多条数据,但是参数**必须都以列表(list - 数组)**的方式
javascript
// 插入一条数据
db.col.insertMany([{"name":"xiaoming"}])
// 插入多条数据
db.col.insertMany([{"name":"xiaoming"}, {"name":"test_user"}])
save
- 如果不指定
_id
,save
的功能与insert
一样。 - 如果指定
_id
,mongodb
就不为该条记录自动生成_id
了。
只有 save
可以指定 _id
的值。
javascript
// 指定id
db.col.save({"_id":ObjectId("5d07461141623d5db6cd4d43"),"name":"xiaoming"})
数据更新
update 方法
update
接收三个参数:
query
: 查询条件,找到指定的数据(document),对其做更新操作update
: 具体的更新操作,数据覆盖,修改options
: 可选参数,其余的插入、修改配置项
javascript
db.collection.update(
<query>, // update的查询条件,类似sql update语句where后面的部分
<update>, // update的对象和一些更新的操作符等,也可以理解为sql update语句set后面的
{
upsert: <boolean>, // 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入
multi: <boolean>, // 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新
writeConcern: <document> // 可选,抛出异常的级别
}
)
基本更新操作符
javascript
// $set - 设置字段值
db.users.update(
{ name: "张三" },
{ $set: { age: 26, city: "上海" } }
)
// $unset - 删除字段
db.users.update(
{ name: "张三" },
{ $unset: { age: "" } }
)
// $inc - 数值增减
db.users.update(
{ name: "张三" },
{ $inc: { age: 1 } }
)
数组操作符
javascript
// $push - 向数组添加元素
db.users.update(
{ name: "张三" },
{ $push: { hobbies: "跑步" } }
)
// $pull - 从数组删除元素
db.users.update(
{ name: "张三" },
{ $pull: { hobbies: "跑步" } }
)
// $addToSet - 添加唯一元素(避免重复)
db.users.update(
{ name: "张三" },
{ $addToSet: { hobbies: "游泳" } }
)
// 向数组添加多个元素
db.users.update(
{ name: "张三" },
{ $push: { hobbies: { $each: ["读书", "跑步"] } } }
)
更新选项
javascript
// multi: true - 更新多条记录
db.users.update(
{ age: { $lt: 30 } },
{ $set: { category: "young" } },
{ multi: true }
)
// upsert: true - 不存在则插入
db.users.update(
{ name: "新用户" },
{ $set: { age: 25 } },
{ upsert: true }
)
save 方法
save
方法通过传入的 document
来替换已有 document
。
javascript
db.col.save({
"_id" : ObjectId("5d0751ef41623d5db6cd4d44"), // 指定_id,新的文档会将旧的文档覆盖
"name" : "xming",
"class" : "c++",
"score" : 80
})
数据删除
数据删除可以使用 deleteOne
、deleteMany
、remove
;remove
不推荐使用。
javascript
// 删除一条记录
db.users.deleteOne({ name: "张三" })
// 删除多条记录
db.users.deleteMany({ age: { $lt: 18 } })
// 删除所有记录
db.users.deleteMany({})
查询操作
基本查询
javascript
// 查询所有
db.users.find()
// 查询一条
db.users.findOne({ name: "张三" })
// 美化输出
db.users.find().pretty()
条件操作符
操作符 | 含义 | 示例 |
---|---|---|
= |
等于 | { age: 25 } |
$lt |
小于 | { age: { $lt: 30 } } |
$lte |
小于等于 | { age: { $lte: 30 } } |
$gt |
大于 | { age: { $gt: 18 } } |
$gte |
大于等于 | { age: { $gte: 18 } } |
$ne |
不等于 | { age: { $ne: 25 } } |
$in |
包含 | { name: { $in: ["张三", "李四"] } } |
$nin |
不包含 | { name: { $nin: ["张三", "李四"] } } |
$exists |
字段存在 | { email: { $exists: true } } |
$regex |
正则匹配 | { name: { $regex: /^张/ } } |
$mod |
取模运算 % | { price: { $mod: [10, 0] } } |
复合查询
javascript
// AND 查询(多个条件同时满足)
db.users.find({
age: { $gte: 18, $lte: 30 },
city: "北京"
})
// OR 查询
db.users.find({
$or: [
{ age: { $lt: 18 } },
{ age: { $gt: 65 } }
]
})
// 查询 18岁,名字 张三和李四 的数据
db.users.find({
age: 18,
$or: [
{ name: '张三' },
{ name: '李四' }
]
})
数组查询
javascript
// 查询数组包含特定元素
db.users.find({ hobbies: "游泳" })
// 查询数组包含多个元素
db.users.find({ hobbies: { $all: ["游泳", "读书"] } })
// 查询数组大小
db.users.find({ hobbies: { $size: 3 } })
// 查询数组元素位置
db.users.find({ "hobbies.0": "游泳" })
嵌套文档查询
javascript
// 查询嵌套字段
db.users.find({ "address.city": "北京" })
// 查询嵌套文档
db.users.find({
"address": {
city: "北京",
district: "朝阳区"
}
})
投影查询
javascript
// 只返回指定字段
db.users.find({}, { name: 1, age: 1 })
// 排除指定字段
db.users.find({}, { password: 0, _id: 0 })
// 嵌套字段投影
db.users.find({}, { "address.city": 1, name: 1 })
排序和分页
javascript
// 排序
db.users.find().sort({ age: -1 }) // 降序
db.users.find().sort({ age: 1 }) // 升序
// 分页
db.users.find()
.sort({ age: -1 })
.skip(10) // 跳过前10条
.limit(5) // 限制返回5条
// 获取总数
db.users.find().count()
// 当使用 limit 方法限制返回的记录数时,默认情况下 count 方法仍然返回全部记录条数
// 如果希望返回限制之后的记录数量,要使用 count(true) 或者 count(非0)
db.my_col.find().limit(1).count() // 1条
db.my_col.find().limit(1).count(true) // 全数
聚合管道
聚合管道是 MongoDB 最强大的功能之一,通过多个阶段处理数据。
常用阶段
阶段 | 作用 | 示例 |
---|---|---|
$match |
筛选文档 | { $match: { age: { $gte: 18 } } } |
$project |
选择/重命名字段 | { $project: { name: 1, age: 1 } } |
$group |
分组统计 | { $group: { _id: "$city", count: { $sum: 1 } } } |
$sort |
排序 | { $sort: { age: -1 } } |
$limit |
限制数量 | { $limit: 10 } |
$skip |
跳过数量 | { $skip: 5 } |
$unwind |
拆分数组 | { $unwind: "$hobbies" } |
$lookup |
关联查询 | { $lookup: { from: "orders", localField: "_id", foreignField: "userId", as: "orders" } } |
聚合示例
基础统计
javascript
// 按城市分组统计用户数量
db.users.aggregate([
{ $group: { _id: "$address.city", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
])
复杂统计
javascript
// 统计每个城市的用户年龄分布
db.users.aggregate([
{ $group: {
_id: "$address.city",
totalUsers: { $sum: 1 },
avgAge: { $avg: "$age" },
maxAge: { $max: "$age" },
minAge: { $min: "$age" }
}},
{ $sort: { totalUsers: -1 } }
])
关联查询
javascript
// 用户和订单关联查询
db.users.aggregate([
{
$lookup: {
from: "orders",
localField: "_id",
foreignField: "userId",
as: "orders"
}
},
{
$project: {
name: 1,
orderCount: { $size: "$orders" },
totalAmount: { $sum: "$orders.amount" }
}
}
])
数组处理
javascript
// 统计每个爱好的用户数量
db.users.aggregate([
{ $unwind: "$hobbies" },
{ $group: { _id: "$hobbies", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
])
复杂聚合示例
javascript
db.col.aggregate([
{$match: {"name": "xiaoming"}}, // 查找 xiaoming 同学的课程成绩
{$group: {
_id: "$name", // 按 name 字段分组
total: {
// $sum求和, $avg、$min、$max,分别表示求平均成绩、最低成绩、最高成绩
$sum: "$score" // 计算每个name的score总和
}
}},
{$project: {
"_id": 0, // 不需要_id字段
"name": 1, // 需要name字段
"count": "$total" // 将 total 字段 重命名为 count
}},
{$sort: {"count": -1, "name": 1}}, // 按分数降序排序;同样分数的,按课程名字升序排序
{$skip: 1}, // 跳过一条数据
{$limit: 1} // 只显示一条数据
])
索引优化
索引类型
单字段索引
javascript
// 创建单字段索引
db.users.createIndex({ name: 1 }) // 升序
db.users.createIndex({ age: -1 }) // 降序
复合索引
javascript
// 创建复合索引
db.users.createIndex({ name: 1, age: -1 })
// 查看查询计划
db.users.find({ name: "张三", age: { $gte: 18 } }).explain()
文本索引
javascript
// 创建文本索引
db.articles.createIndex({ title: "text", content: "text" })
// 文本搜索
db.articles.find({ $text: { $search: "MongoDB 教程" } })
地理空间索引
javascript
// 创建2dsphere索引
db.places.createIndex({ location: "2dsphere" })
// 地理空间查询
db.places.find({
location: {
$near: {
$geometry: { type: "Point", coordinates: [116.3974, 39.9093] },
$maxDistance: 1000
}
}
})
索引管理
javascript
// 查看索引
db.users.getIndexes()
// 删除索引
db.users.dropIndex("name_1")
// 删除所有索引
db.users.dropIndexes()
// 重建索引
db.users.reIndex()
// 查看集合索引大小
db.col.totalIndexSize()
创建索引选项
javascript
db.collection.createIndex(
{key1: option1, key2: option2}, //key为要创建索引的字段,option为创建索引的方式:1 为升序,-1 为降序,可以对多个字段创建索引,称为复合索引
{
background: <boolean>, //可选,建索引过程会阻塞其它数据库操作,background 设置为 true 可指定以后台方式创建索引,默认值为 false
unique: <boolean>, //可选,建立的索引是否唯一。指定为true创建唯一索引。默认值为false
name: <string>, //可选,索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称
sparse: <boolean> //可选,对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档。默认值为 false
}
)
索引优化建议
- 为经常查询的字段创建索引
- 复合索引字段顺序很重要:等值查询字段在前,范围查询字段在后
- 避免过多索引:索引会占用存储空间并影响写入性能
- 使用 explain() 分析查询性能
高级特性
事务处理
javascript
// 开始事务
const session = db.getMongo().startSession()
session.startTransaction()
try {
// 执行操作
db.users.insertOne({ name: "张三", age: 25 }, { session })
db.orders.insertOne({ userId: ObjectId(), amount: 100 }, { session })
// 提交事务
session.commitTransaction()
} catch (error) {
// 回滚事务
session.abortTransaction()
} finally {
session.endSession()
}
副本集
MongoDB副本集是将数据同步在多个服务器的过程。
副本集提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性,并可以保证数据的安全性。
副本集还允许您从硬件故障和服务中断中恢复数据。
主节点会记录所有的操作oplog,从节点定期轮训oplog,对自己的副本执行这些操作,从而保证数据与主节点保持一致。
副本集由一个主节点和多个从节点组成。节点数量最多50个,推荐节点数是奇数个。
副本集的主节点挂掉后,会在集群内发起主节点的选举机制,从副节点中选举一个新的主节点出来。
分片集群
配置分片
javascript
// 启用分片
sh.enableSharding("mydb")
// 设置分片键
sh.shardCollection("mydb.users", { userId: 1 })
// 查看分片状态
sh.status()
数据备份和恢复
bash
# 备份数据库
mongodump --db mydb --out /backup/
# 恢复数据库
mongorestore --db mydb /backup/mydb/
# 压缩备份
mongodump --db mydb --gzip --archive=/backup/mydb.gz
# 备份指定集合
mongodump --db mydb --collection users --out /backup/
# 从压缩文件恢复
mongorestore --gzip --archive=/backup/mydb.gz
监控和维护
javascript
// 查看数据库状态
db.stats()
// 查看集合状态
db.users.stats()
// 查看慢查询
db.setProfilingLevel(1, { slowms: 100 })
db.system.profile.find().sort({ ts: -1 }).limit(5)
// 查看当前操作
db.currentOp()
// 查看内存使用情况
db.serverStatus().mem
// 查看索引使用情况
db.users.aggregate([{ $indexStats: {} }])
// 重建索引
db.users.reIndex()
// 压缩集合
db.runCommand({ compact: "users" })
// 验证集合
db.users.validate()
安全配置
javascript
// 创建用户
use admin
db.createUser({
user: "admin",
pwd: "password",
roles: ["userAdminAnyDatabase", "dbAdminAnyDatabase", "readWriteAnyDatabase"]
})
// 创建普通用户
use mydb
db.createUser({
user: "appuser",
pwd: "apppassword",
roles: ["readWrite"]
})
// 启用认证
// 启动时添加 --auth 参数
mongod --auth
性能优化
查询优化
javascript
// 使用 explain() 分析查询性能
db.users.find({ name: "张三" }).explain("executionStats")
// 使用 hint() 强制使用特定索引
db.users.find({ name: "张三" }).hint({ name: 1 })
批量操作
javascript
// 批量插入
db.users.insertMany([
{ name: "张三", age: 25 },
{ name: "李四", age: 30 },
{ name: "王五", age: 28 }
])
// 批量更新
db.users.bulkWrite([
{ updateOne: { filter: { name: "张三" }, update: { $set: { age: 26 } } } },
{ updateOne: { filter: { name: "李四" }, update: { $set: { age: 31 } } } }
])
NestJS 集成
安装和配置
bash
npm install @nestjs/mongoose mongoose
npm install -D @types/mongoose
基本配置
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost:27017/mydb'),
],
})
export class AppModule {}
环境变量配置
typescript
// app.module.ts
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [
ConfigModule.forRoot(),
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
uri: configService.get<string>('MONGODB_URI'),
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}
Schema 定义
typescript
// user.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types } from 'mongoose';
export type UserDocument = User & Document;
@Schema({
timestamps: true,
collection: 'users',
versionKey: false
})
export class User {
@Prop({ type: Types.ObjectId, auto: true })
_id: Types.ObjectId;
@Prop({ required: true, trim: true, maxlength: 50 })
name: string;
@Prop({
required: true,
unique: true,
lowercase: true,
match: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
})
email: string;
@Prop({ min: 0, max: 150 })
age: number;
@Prop({
type: [String],
default: [],
validate: {
validator: function(v: string[]) {
return v.length <= 10;
},
message: 'Hobbies cannot exceed 10 items'
}
})
hobbies: string[];
@Prop({
type: {
city: { type: String, required: true },
district: String,
coordinates: {
type: [Number],
index: '2dsphere'
}
}
})
address: {
city: string;
district?: string;
coordinates?: [number, number];
};
@Prop({
type: Date,
default: Date.now,
expires: 86400 // 24小时后过期
})
lastLogin: Date;
@Prop({ default: false })
isDeleted: boolean;
@Prop()
deletedAt: Date;
}
export const UserSchema = SchemaFactory.createForClass(User);
// 添加索引
UserSchema.index({ email: 1 });
UserSchema.index({ 'address.city': 1, age: -1 });
UserSchema.index({ lastLogin: 1 }, { expireAfterSeconds: 0 });
// 添加虚拟字段
UserSchema.virtual('fullAddress').get(function() {
return `${this.address.city} ${this.address.district || ''}`.trim();
});
// 添加方法
UserSchema.methods.isAdult = function() {
return this.age >= 18;
};
// 添加静态方法
UserSchema.statics.findByCity = function(city: string) {
return this.find({ 'address.city': city });
};
Service 实现
typescript
// user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel, InjectConnection } from '@nestjs/mongoose';
import { Model, Connection } from 'mongoose';
import { User, UserDocument } from './user.schema';
@Injectable()
export class UserService {
constructor(
@InjectModel(User.name) private userModel: Model<UserDocument>,
@InjectConnection() private connection: Connection,
) {}
async create(createUserDto: any): Promise<User> {
try {
const user = new this.userModel(createUserDto);
return await user.save();
} catch (error) {
if (error.code === 11000) {
throw new ConflictException('Email already exists');
}
throw new InternalServerErrorException('Failed to create user');
}
}
async findAll(): Promise<User[]> {
return this.userModel.find().exec();
}
async findOne(id: string): Promise<User> {
return this.userModel.findById(id).exec();
}
async update(id: string, updateUserDto: any): Promise<User> {
return this.userModel.findByIdAndUpdate(id, updateUserDto, { new: true }).exec();
}
async remove(id: string): Promise<User> {
return this.userModel.findByIdAndDelete(id).exec();
}
// 分页查询
async findWithPagination(page: number = 1, limit: number = 10, filters: any = {}) {
const skip = (page - 1) * limit;
const [users, total] = await Promise.all([
this.userModel.find(filters).skip(skip).limit(limit).exec(),
this.userModel.countDocuments(filters).exec()
]);
return {
data: users,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
};
}
// 复杂查询示例
async findByAgeRange(minAge: number, maxAge: number): Promise<User[]> {
return this.userModel.find({
age: { $gte: minAge, $lte: maxAge }
}).exec();
}
// 聚合查询示例
async getUsersByCity(): Promise<any[]> {
return this.userModel.aggregate([
{ $group: { _id: '$address.city', count: { $sum: 1 } } },
{ $sort: { count: -1 } }
]).exec();
}
// 软删除
async softDelete(id: string): Promise<User> {
return this.userModel.findByIdAndUpdate(
id,
{ isDeleted: true, deletedAt: new Date() },
{ new: true }
).exec();
}
async findActive(): Promise<User[]> {
return this.userModel.find({ isDeleted: false }).exec();
}
// 事务处理
async createUserWithProfile(userData: any, profileData: any): Promise<User> {
const session = await this.connection.startSession();
try {
session.startTransaction();
const user = new this.userModel(userData);
await user.save({ session });
// 创建关联的profile
const profile = new this.profileModel({ ...profileData, userId: user._id });
await profile.save({ session });
await session.commitTransaction();
return user;
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
session.endSession();
}
}
// 性能优化方法
async findAllLean(): Promise<any[]> {
return this.userModel.find().lean().exec();
}
async findNamesOnly(): Promise<any[]> {
return this.userModel.find().select('name email').exec();
}
}
Controller 实现
typescript
// user.controller.ts
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
create(@Body() createUserDto: any) {
return this.userService.create(createUserDto);
}
@Get()
findAll(@Query('page') page?: string, @Query('limit') limit?: string, @Query('minAge') minAge?: string, @Query('maxAge') maxAge?: string) {
if (minAge && maxAge) {
return this.userService.findByAgeRange(+minAge, +maxAge);
}
if (page || limit) {
return this.userService.findWithPagination(+page || 1, +limit || 10);
}
return this.userService.findAll();
}
@Get('stats')
getStats() {
return this.userService.getUsersByCity();
}
@Get('active')
findActive() {
return this.userService.findActive();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.userService.findOne(id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: any) {
return this.userService.update(id, updateUserDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.userService.remove(id);
}
@Delete(':id/soft')
softRemove(@Param('id') id: string) {
return this.userService.softDelete(id);
}
}
数据验证
typescript
// create-user.dto.ts
import { IsEmail, IsString, IsNumber, IsOptional, IsArray, Min, Max, ArrayMaxSize } from 'class-validator';
export class CreateUserDto {
@IsString()
@IsOptional()
name?: string;
@IsEmail()
email: string;
@IsNumber()
@Min(0)
@Max(150)
@IsOptional()
age?: number;
@IsArray()
@ArrayMaxSize(10)
@IsOptional()
hobbies?: string[];
@IsOptional()
address?: {
city: string;
district?: string;
};
}
连接池配置
typescript
// app.module.ts
MongooseModule.forRootAsync({
useFactory: () => ({
uri: 'mongodb://localhost:27017/mydb',
useNewUrlParser: true,
useUnifiedTopology: true,
maxPoolSize: 10, // 最大连接数
minPoolSize: 5, // 最小连接数
maxIdleTimeMS: 30000, // 最大空闲时间
serverSelectionTimeoutMS: 5000, // 服务器选择超时
socketTimeoutMS: 45000, // Socket超时
bufferMaxEntries: 0, // 禁用mongoose缓冲
bufferCommands: false, // 禁用mongoose缓冲
}),
}),
数据迁移
typescript
// migration.service.ts
@Injectable()
export class MigrationService {
constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}
async migrateUserData() {
const users = await this.userModel.find({ address: { $exists: false } });
for (const user of users) {
await this.userModel.updateOne(
{ _id: user._id },
{ $set: { address: { city: 'Unknown' } } }
);
}
}
}
最佳实践
1. 数据建模
- 嵌入 vs 引用:小文档且不经常变化的数据适合嵌入,大文档或经常变化的数据适合引用
- 避免深层嵌套:嵌套层级不要超过3层
- 合理使用数组:数组元素不要超过100个
2. 查询优化
typescript
// 使用 lean() 提高查询性能
async findAllLean(): Promise<any[]> {
return this.userModel.find().lean().exec();
}
// 使用 select() 只选择需要的字段
async findNamesOnly(): Promise<any[]> {
return this.userModel.find().select('name email').exec();
}
// 使用 explain() 分析查询性能
db.users.find({ name: "张三" }).explain("executionStats")
3. 错误处理
typescript
async create(createUserDto: CreateUserDto): Promise<User> {
try {
const user = new this.userModel(createUserDto);
return await user.save();
} catch (error) {
if (error.code === 11000) {
throw new ConflictException('Email already exists');
}
throw new InternalServerErrorException('Failed to create user');
}
}
4. 性能监控
javascript
// 查看慢查询
db.setProfilingLevel(1, { slowms: 100 })
// 查看索引使用情况
db.users.aggregate([{ $indexStats: {} }])
// 查看内存使用情况
db.serverStatus().mem
5. 安全建议
- 启用认证和授权
- 使用 SSL/TLS 加密连接
- 定期备份数据
- 监控数据库访问日志
- 使用强密码和定期更换
6. 常见问题
- 连接超时:检查网络连接和防火墙设置
- 查询慢:分析查询计划,添加合适的索引
- 内存不足:调整缓存大小,优化查询
- 数据不一致:使用事务保证数据一致性
7. 连接管理
typescript
// 使用连接池
const connectionOptions = {
maxPoolSize: 10,
minPoolSize: 5,
maxIdleTimeMS: 30000,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
};
总结
MongoDB 是一个功能强大的 NoSQL 数据库,特别适合处理非结构化数据和需要快速开发的应用。通过合理使用索引、聚合管道和最佳实践,可以构建高性能的应用程序。
与 NestJS 的集成提供了类型安全和良好的开发体验,是现代 Node.js 应用开发的优秀选择。
关键要点
- 灵活的数据模型:支持复杂的数据结构和嵌套文档
- 强大的查询能力:丰富的查询操作符和聚合管道
- 高性能:通过索引和优化策略实现高性能查询
- 可扩展性:支持副本集和分片集群
- 开发友好:与 NestJS 等现代框架完美集成