MongoDB: 删除文档与Node.js入门
最后更新:2026-08-26
删除文档是数据清理的核心操作——本课程同时引入 Node.js + mongoose 实战。
掌握 deleteOne/deleteMany、Node.js + mongoose 的连接、Schema 定义、Model CRUD。
1. 你将学到
- deleteOne / deleteMany 删除文档
- findOneAndDelete 原子返回被删除文档
- drop 集合与 dropDatabase 删除数据库
- Node.js + mongoose 连接 MongoDB
- mongoose Schema 定义与 Model 创建
- mongoose CRUD 操作(Create/Read/Update/Delete)
2. 一个全栈工程师的真实故事
(1) 痛点:清理过期数据需要复杂脚本
Bob 需要定期清理电商平台的过期订单和已注销用户:
// ❌ 反例:多次查询再删除(慢 + 竞态)
const expiredOrders = await Order.find({ expiryDate: { $lt: new Date() } });
for (const order of expiredOrders) {
await Order.deleteOne({ _id: order._id });
}
// N 次网络往返,N 次删除操作
(2) MongoDB + mongoose 批量删除的解法
// ✅ 正例:一次性批量删除
const result = await Order.deleteMany({
expiryDate: { $lt: new Date() }
});
// 单次操作删除所有过期订单
// mongoose Schema 定义
const OrderSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, required: true },
items: [{ sku: String, qty: Number, price: mongoose.Schema.Types.Decimal128 }],
total: { type: mongoose.Schema.Types.Decimal128, required: true },
status: { type: String, enum: ['pending', 'paid', 'shipped', 'delivered'], default: 'pending' },
expiryDate: Date,
createdAt: { type: Date, default: Date.now }
}, { timestamps: true });
const Order = mongoose.model('Order', OrderSchema);
graph LR
A[删除操作] --> B[deleteOne<br/>删除单个]
A --> C[deleteMany<br/>批量删除]
A --> D[findOneAndDelete<br/>原子返回]
A --> E[drop<br/>删除集合]
A --> F[dropDatabase<br/>删除数据库]
style D fill:#d4edda
3. deleteOne 删除单个文档
概念说明:deleteOne 根据过滤条件删除第一个匹配的文档,是 MongoDB 最基础的删除方法。删除操作不可逆——没有"回收站"机制,被删除的文档无法直接恢复(除非有备份或 oplog)。因此在生产环境中,删除操作需要格外谨慎。
工作原理:deleteOne 执行流程为:匹配阶段(根据 filter 找到第一个文档)→ 删除阶段(从集合中移除文档)→ 索引更新(删除相关索引条目)→ Write Concern 确认。整个操作对单文档是原子的。删除后磁盘空间不会立即释放,而是标记为可重用空间。
sequenceDiagram
participant App as 应用程序
participant Mongo as MongoDB
participant WT as WiredTiger
App->>Mongo: deleteOne({ sku: "PHONE-001" })
Mongo->>Mongo: 匹配 filter(索引扫描)
Mongo->>WT: 删除文档 + 更新索引
WT-->>Mongo: 确认删除
Mongo-->>App: { acknowledged: true, deletedCount: 1 }
| 参数 | 类型 | 说明 |
|---|---|---|
filter |
Document | 匹配条件(必填) |
options |
Document | writeConcern 等(可选) |
| 返回字段 | 类型 | 说明 |
|---|---|---|
acknowledged |
Boolean | 是否已确认写入 |
deletedCount |
Number | 删除的文档数(0 或 1) |
// === deleteOne 基本用法 ===
db.products.deleteOne({ sku: 'PHONE-001' });
// 返回结果:
// { acknowledged: true, deletedCount: 1 }
| 返回字段 | 含义 |
|---|---|
acknowledged |
是否已确认 |
deletedCount |
删除的文档数(0 或 1) |
▶ 示例 1:deleteOne 实战
// === 删除指定 _id 的文档 ===
db.users.deleteOne({ _id: ObjectId('507f1f77bcf86cd799439011') });
// === 删除指定条件(第一个匹配)===
db.logs.deleteOne({ level: 'debug' });
// === mongoose 等价 ===
const result = await Product.deleteOne({ sku: 'PHONE-001' });
console.log(result.deletedCount); // 1
输出:
// mongoose 操作成功执行
// 数据库查询/更新结果
4. deleteMany 批量删除
概念说明:deleteMany 根据过滤条件删除所有匹配的文档,是批量数据清理的核心方法。与 deleteOne 只删除第一个匹配不同,deleteMany 可以一次删除成千上万条文档。常见场景包括:清理过期日志、删除已注销用户数据、移除测试数据等。
工作原理:deleteMany 先查询所有匹配文档,再逐个删除。删除过程不是事务性的——如果中途失败,已删除的文档不会恢复。大批量删除建议分批执行,避免长时间锁定集合。
| 维度 | deleteOne | deleteMany |
|---|---|---|
| 匹配范围 | 第一个匹配 | 所有匹配 |
| 删除数量 | 0 或 1 | 0 到 N |
| 适用场景 | 精确删除单条 | 批量清理 |
| 风险 | 低 | 较高(误操作影响大) |
// === deleteMany 基本用法 ===
db.products.deleteMany({ category: 'Discontinued' });
// === 删除所有过期订单 ===
db.orders.deleteMany({
expiryDate: { $lt: new Date() }
});
// === 删除特定条件的多个文档 ===
db.logs.deleteMany({
level: { $in: ['debug', 'info'] },
createdAt: { $lt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) }
});
▶ 示例 2:deleteMany 实战
// === 清理 30 天前的日志 ===
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const result = await Log.deleteMany({ createdAt: { $lt: thirtyDaysAgo } });
console.log(`Deleted ${result.deletedCount} old logs`);
// === 删除已注销用户的会话 ===
await Session.deleteMany({ userId: deletedUserId });
// === 删除整个集合的所有文档(慎用!)===
db.products.deleteMany({});
// ⚠️ 这会删除 products 集合的所有文档
输出:
// 执行成功
5. findOneAndDelete 原子返回
概念说明:findOneAndDelete 是一种特殊的删除方法,它在删除文档的同时返回被删除的文档内容。这解决了"先查后删"的竞态条件问题——传统做法需要先 findOne 获取文档、再 deleteOne 删除,两步之间文档可能被其他操作修改或删除。findOneAndDelete 将查询和删除合并为一次原子操作。
工作原理:findOneAndDelete 在文档级别执行原子操作:找到匹配文档 → 记录文档内容 → 删除文档 → 返回记录的内容。默认返回删除前的文档状态,可通过 projection 选项控制返回字段。
graph TB
A[需要删除并获取文档] --> B{方法选择}
B --> C[❌ 先查后删<br/>findOne + deleteOne<br/>竞态条件风险]
B --> D[✅ findOneAndDelete<br/>原子操作<br/>无竞态风险]
B --> E[✅ findOneAndDelete + sort<br/>原子操作 + 顺序<br/>FIFO 队列]
style D fill:#d4edda
style E fill:#d4edda
| 优势 | 说明 |
|---|---|
| 原子性 | 查询 + 删除一次完成,避免竞态 |
| 返回文档 | 直接拿到被删除的文档,无需二次查询 |
| 排序支持 | 配合 sort 选项实现有序消费 |
| 适用场景 | 队列任务、消息消费、库存扣减 |
// === findOneAndDelete 原子操作 ===
const deletedDoc = db.products.findOneAndDelete({ sku: 'PHONE-001' });
// 返回被删除的文档(默认删除前的状态)
console.log(deletedDoc);
// { _id: ..., sku: 'PHONE-001', title: 'Phone', price: 599, ... }
// === 不存在时返回 null ===
const result = db.products.findOneAndDelete({ sku: 'NOT_EXIST' });
console.log(result); // null
| 优势 | 说明 |
|---|---|
| 原子性 | 查询 + 删除一次完成,避免竞态 |
| 返回文档 | 直接拿到被删除的文档,无需二次查询 |
| 适用场景 | 队列任务、消息消费、库存扣减 |
▶ 示例 3:findOneAndDelete 实战
// === 场景:消息队列(FIFO)===
const message = await Queue.findOneAndDelete(
{ status: 'pending' },
{ sort: { createdAt: 1 } } // 优先消费最早的
);
// === 场景:任务领取 ===
const task = await Task.findOneAndDelete({
status: 'available',
assignee: null
});
if (task) {
console.log(`Claimed task: ${task._id}`);
}
// === mongoose 等价 ===
const message = await Queue.findOneAndDelete(
{ status: 'pending' },
{ sort: { createdAt: 1 } }
);
输出:
// mongoose 操作成功执行
// 数据库查询/更新结果
6. drop 集合与 dropDatabase
概念说明:drop 和 dropDatabase 是最彻底的删除操作——drop 删除整个集合(包括所有文档和索引),dropDatabase 删除整个数据库(包括所有集合)。与 deleteMany({}) 不同,drop 操作不仅删除数据,还删除集合的元数据(索引定义、Schema 验证规则、capped 设置等)。
对比分析:
| 维度 | deleteMany({}) | drop() | dropDatabase() |
|---|---|---|---|
| 删除范围 | 集合内所有文档 | 整个集合 | 整个数据库 |
| 保留索引 | ✅ 保留 | ❌ 全部删除 | ❌ 全部删除 |
| 保留 capped 设置 | ✅ 保留 | ❌ 删除 | ❌ 删除 |
| 保留 Schema 验证 | ✅ 保留 | ❌ 删除 | ❌ 删除 |
| 速度 | 慢(逐条删除) | 快(直接释放空间) | 快 |
| 可恢复性 | 可通过 oplog 恢复 | 极难恢复 | 极难恢复 |
// === 删除集合 ===
db.products.drop();
// true(成功)或 false(集合不存在)
// === 删除数据库 ===
db.dropDatabase();
// { "dropped" : "shopdb", "ok" : 1 }
// === 慎用:删除整个集合的所有数据但保留集合 ===
db.products.deleteMany({});
// 等效但保留集合结构(索引、capped 设置)
▶ 示例 4:drop vs deleteMany 选择
// 场景:临时测试集合清理
// ✅ 推荐:drop(删除集合+索引,干净)
db.test_results.drop();
// ✅ 保留集合结构:deleteMany(仅清空文档)
db.user_sessions.deleteMany({});
输出:
// 执行成功
7. Node.js + mongoose 连接 MongoDB
概念说明:mongoose 是 Node.js 生态最流行的 MongoDB ODM(Object Document Modeling),提供 Schema 定义、数据校验、中间件、关联查询等高级功能。本节从连接 MongoDB 开始,逐步引入 mongoose 的核心概念。
工作原理:mongoose 连接 MongoDB 的过程分为:创建连接实例 → 建立 TCP 连接 → 认证(如需)→ 选择数据库 → 连接池初始化 → 触发 connected 事件。mongoose 默认维护一个连接池(默认 5-100 个连接),复用连接避免频繁建立/断开 TCP 连接。
sequenceDiagram
participant App as Node.js 应用
participant Mongoose as mongoose
participant Mongo as MongoDB
App->>Mongoose: mongoose.connect(uri)
Mongoose->>Mongo: 建立 TCP 连接
Mongo-->>Mongoose: 连接确认
Mongoose->>Mongo: 认证(如需)
Mongo-->>Mongoose: 认证成功
Mongoose->>Mongoose: 初始化连接池
Mongoose-->>App: 触发 'connected' 事件
Note over App,Mongoose: 连接就绪,可执行 CRUD
| 连接方式 | URI 格式 | 适用场景 |
|---|---|---|
| 本地单机 | mongodb://localhost:27017/shopdb |
开发环境 |
| Atlas 云 | mongodb+srv://user:pass@cluster0.mongodb.net/mydb |
生产/团队 |
| 副本集 | mongodb://host1,host2,host3/shopdb?replicaSet=rs0 |
生产环境 |
| Docker | mongodb://192.168.1.100:27017/shopdb |
容器化部署 |
(1) 安装 mongoose
npm install mongoose --save
(2) 连接 MongoDB
// === 基本连接 ===
const mongoose = require('mongoose');
async function connectDB() {
await mongoose.connect('mongodb://localhost:27017/shopdb');
console.log('✅ MongoDB connected');
}
connectDB().catch(err => console.error('❌ Connection error:', err));
(3) 连接选项
概念说明:mongoose 连接选项控制连接行为的关键参数——超时时间、连接池大小、认证方式等。生产环境必须合理配置这些选项,否则可能导致连接泄漏、超时失败或认证错误。
核心参数说明:
| 参数 | 默认值 | 说明 | 生产推荐 |
|---|---|---|---|
serverSelectionTimeoutMS |
30000 | 服务器选择超时(毫秒) | 5000 |
socketTimeoutMS |
30000 | 套接字超时 | 45000 |
maxPoolSize |
100 | 最大连接池大小 | 50-100 |
minPoolSize |
0 | 最小连接池大小 | 5 |
heartbeatFrequencyMS |
10000 | 心跳检测频率 | 10000 |
retryWrites |
true | 自动重试写入 | true |
authSource |
admin | 认证数据库 | 视配置 |
连接池原理:mongoose 维护一个 TCP 连接池,避免频繁创建/销毁连接的开销。每个并发请求从池中获取一个连接,执行完毕后归还。maxPoolSize 控制最大并发数——设置过小会导致请求排队等待,设置过大会占用过多服务端资源。
// === 完整连接配置 ===
await mongoose.connect('mongodb://localhost:27017/shopdb', {
// 服务器选择超时
serverSelectionTimeoutMS: 5000,
// 套接字超时
socketTimeoutMS: 45000,
// 连接池大小
maxPoolSize: 50,
minPoolSize: 5,
// 自动重连
autoReconnect: true,
// 认证(如果启用)
user: 'admin',
pass: 'password',
// 认证数据库
authSource: 'admin'
});
(4) 连接 Atlas
// === Atlas 连接字符串 ===
await mongoose.connect(
'mongodb+srv://user:pass@cluster0.mongodb.net/mydb?retryWrites=true&w=majority'
);
// === 带环境变量 ===
require('dotenv').config();
await mongoose.connect(process.env.MONGODB_URI);
▶ 示例 5:完整连接管理
// db.js
const mongoose = require('mongoose');
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/shopdb', {
serverSelectionTimeoutMS: 5000,
maxPoolSize: 50
});
console.log(`✅ MongoDB connected: ${conn.connection.host}`);
// 监听连接事件
mongoose.connection.on('error', (err) => console.error('❌ MongoDB error:', err));
mongoose.connection.on('disconnected', () => console.warn('⚠️ MongoDB disconnected'));
mongoose.connection.on('reconnected', () => console.log('🔄 MongoDB reconnected'));
} catch (err) {
console.error('❌ Connection failed:', err.message);
process.exit(1);
}
};
const disconnectDB = async () => {
await mongoose.disconnect();
console.log('MongoDB disconnected');
};
module.exports = { connectDB, disconnectDB, mongoose };
输出:
❌ MongoDB error:
⚠️ MongoDB disconnected
🔄 MongoDB reconnected
❌ Connection failed:
MongoDB disconnected
8. mongoose Schema 定义
概念说明:Schema 是 mongoose 的核心概念——它定义文档的字段类型、验证规则、默认值、索引等。虽然 MongoDB 本身是 schema-less 的,但 mongoose 在应用层提供 Schema 约束,防止脏数据和类型错误。Schema 定义后编译为 Model,Model 是操作数据库的接口。
工作原理:Schema → Model → Document 是 mongoose 的三层架构。Schema 定义结构(字段类型+验证),Model 是 Schema 的编译产物(对应一个集合),Document 是 Model 的实例(对应一条文档)。Schema 不直接操作数据库,只有通过 Model 才能执行 CRUD。
Schema 选项:Schema 构造函数的第二个参数控制全局行为——timestamps: true 自动添加 createdAt/updatedAt,strict: true 忽略未声明字段,versionKey: false 去掉 __v 版本键。
graph LR
A[Schema<br/>定义字段类型+验证] -->|mongoose.model| B[Model<br/>操作集合的接口]
B -->|new Model| C[Document<br/>一条文档实例]
B -->|Model.find| D[查询结果<br/>Document 数组]
style A fill:#cce5ff
style B fill:#d4edda
| Schema 字段选项 | 类型 | 说明 | 示例 |
|---|---|---|---|
type |
Constructor | 字段类型 | String, Number, Date |
required |
Boolean/Array | 是否必填 | [true, 'Email is required'] |
default |
Any/Function | 默认值 | Date.now, 0, true |
unique |
Boolean | 是否创建唯一索引 | true |
index |
Boolean/Object | 是否建索引 | true, { sparse: true } |
enum |
Array | 允许的值列表 | ['pending', 'paid'] |
min / max |
Number | 数值范围 | min: 0, max: 999999 |
minlength / maxlength |
Number | 字符串长度范围 | minlength: 3 |
match |
RegExp | 正则验证 | /^.+@.+$/ |
select |
Boolean | 默认查询是否返回 | false(如 passwordHash) |
validate |
Function | 自定义验证函数 | v => v.length >= 8 |
get / set |
Function | 虚拟 getter/setter | get: v => v.toString() |
(1) 基本 Schema
// === Schema 定义用户模型 ===
const UserSchema = new mongoose.Schema({
// 字段定义
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true
},
username: {
type: String,
required: true,
unique: true,
minlength: 3,
maxlength: 30
},
passwordHash: {
type: String,
required: true,
select: false // 默认查询不返回
},
age: {
type: Number,
min: 0,
max: 150
},
role: {
type: String,
enum: ['customer', 'admin', 'moderator'],
default: 'customer'
},
isActive: {
type: Boolean,
default: true
}
}, {
// Schema 选项
timestamps: true, // 自动添加 createdAt/updatedAt
collection: 'users', // 显式指定集合名
strict: true, // 严格模式(不保存未声明字段)
versionKey: false // 禁用 __v
});
(2) Schema 类型
const ProductSchema = new mongoose.Schema({
// 字符串
sku: String,
// 数字
stock: Number,
price: mongoose.Schema.Types.Decimal128,
// 日期
releaseDate: Date,
// 布尔
isActive: Boolean,
// 数组
tags: [String],
// 嵌套文档
specs: {
screen: String,
battery: String
},
// Buffer(二进制)
thumbnail: Buffer,
// ObjectId 引用
categoryId: mongoose.Schema.Types.ObjectId,
// 混合类型(任意)
metadata: mongoose.Schema.Types.Mixed,
// Map(键值对)
translations: {
type: Map,
of: String
}
});
▶ 示例 6:综合 Schema 设计
const ProductSchema = new mongoose.Schema({
sku: {
type: String,
required: [true, 'SKU is required'],
unique: true,
index: true,
match: /^[A-Z0-9-]+$/
},
title: {
type: String,
required: true,
trim: true,
maxlength: 200
},
description: {
type: String,
maxlength: 5000
},
price: {
type: mongoose.Schema.Types.Decimal128,
required: true,
min: 0,
get: v => v ? v.toString() : v // 序列化为字符串
},
category: {
type: String,
enum: ['Electronics', 'Books', 'Clothing', 'Home'],
required: true,
index: true
},
tags: [String],
attributes: {
type: Map,
of: mongoose.Schema.Types.Mixed,
default: {}
},
stock: {
type: Number,
default: 0,
min: 0
},
rating: {
type: Number,
default: 0,
min: 0,
max: 5
},
isActive: {
type: Boolean,
default: true,
index: true
}
}, {
timestamps: true,
toJSON: { virtuals: true, getters: true },
toObject: { virtuals: true }
});
// 虚拟字段
ProductSchema.virtual('isInStock').get(function() {
return this.stock > 0;
});
// 索引
ProductSchema.index({ category: 1, price: 1 });
ProductSchema.index({ title: 'text', description: 'text' });
const Product = mongoose.model('Product', ProductSchema);
输出:
// mongoose 操作成功执行
// 数据库查询/更新结果
9. mongoose CRUD 操作
概念说明:CRUD(Create/Read/Update/Delete)是数据库操作的基本模式。mongoose 提供了两种 CRUD 风格——Model 静态方法(如 Model.create()、Model.find())和 Document 实例方法(如 doc.save()、doc.remove())。静态方法直接操作数据库,实例方法先修改内存对象再同步到数据库。
对比分析:
| 维度 | Model 静态方法 | Document 实例方法 |
|---|---|---|
| 调用方式 | Model.create(data) |
new Model(data); doc.save() |
| 触发验证 | ✅ | ✅ |
| 触发中间件 | 部分 | ✅ 全部 |
| 返回值 | Document 或结果对象 | Document |
| 适用场景 | 简单 CRUD | 复杂业务逻辑 |
graph TB
A[mongoose CRUD] --> B[Create<br/>create() / save()]
A --> C[Read<br/>find() / findOne() / findById()]
A --> D[Update<br/>updateOne() / findByIdAndUpdate()]
A --> E[Delete<br/>deleteOne() / findByIdAndDelete()]
style A fill:#cce5ff
(1) Create 创建
// === model.create() 创建单个文档 ===
const user = await User.create({
email: 'alice@example.com',
username: 'alice_chen',
passwordHash: 'hashed_password',
age: 28
});
console.log(user._id); // ObjectId
// === 创建多个文档 ===
const users = await User.create([
{ email: 'bob@example.com', username: 'bob' },
{ email: 'charlie@example.com', username: 'charlie' }
]);
// === new + save 模式 ===
const user = new User({
email: 'alice@example.com',
username: 'alice_chen'
});
await user.save();
(2) Read 读取
// === find 查询多个 ===
const users = await User.find({ isActive: true });
// === findOne 查询单个 ===
const user = await User.findOne({ email: 'alice@example.com' });
// === findById 通过 _id 查询 ===
const user = await User.findById('507f1f77bcf86cd799439011');
// === 链式查询 ===
const products = await Product.find({ category: 'Electronics' })
.select('sku title price')
.sort({ price: 1 })
.limit(20)
.lean();
(3) Update 更新
// === findByIdAndUpdate ===
const user = await User.findByIdAndUpdate(
userId,
{ $set: { lastLoginAt: new Date() } },
{ new: true, runValidators: true } // 返回更新后文档
);
// === updateOne ===
const result = await User.updateOne(
{ email: 'alice@example.com' },
{ $set: { age: 29 } }
);
// === save() 完整文档替换 ===
const user = await User.findById(userId);
user.age = 30;
await user.save();
(4) Delete 删除
// === findByIdAndDelete ===
const user = await User.findByIdAndDelete(userId);
// === deleteOne ===
const result = await User.deleteOne({ email: 'alice@example.com' });
// === deleteMany ===
const result = await User.deleteMany({ isActive: false });
▶ 示例 7:完整 CRUD 实战
// === 1. 创建用户 ===
const alice = await User.create({
email: 'alice@example.com',
username: 'alice_chen',
passwordHash: await bcrypt.hash('password123', 10),
age: 28
});
// === 2. 查询用户 ===
const users = await User.find({ age: { $gte: 18 } })
.select('email username age')
.lean();
// === 3. 更新用户 ===
await User.updateOne(
{ _id: alice._id },
{ $set: { lastLoginAt: new Date() }, $inc: { loginCount: 1 } }
);
// === 4. 删除测试用户 ===
await User.deleteMany({ email: { $regex: '@test\\.com$' } });
输出:
// mongoose 操作成功执行
// 数据库查询/更新结果
10. 常见错误排查
概念说明:Node.js + mongoose 开发中最常见的四类错误:连接错误(ECONNREFUSED)、唯一索引冲突(E11000)、Schema 验证失败(ValidationError)和类型转换失败(CastError)。理解这些错误的根本原因和处理方式是 mongoose 开发的基本功。
错误处理策略:连接错误需要重试机制,唯一索引冲突需要 upsert 或前端校验,Schema 验证失败需要完善表单校验,CastError 需要检查 ObjectId 格式。所有错误都应在应用层捕获并返回友好的错误消息——不要将原始的 MongoDB 错误堆栈暴露给前端用户。
graph TB
A[mongoose 错误] --> B[连接错误<br/>ECONNREFUSED]
A --> C[唯一索引冲突<br/>E11000]
A --> D[验证失败<br/>ValidationError]
A --> E[类型转换失败<br/>CastError]
B --> B1[检查 mongod 是否启动<br/>检查连接字符串]
C --> C1[使用 upsert<br/>前端唯一性校验]
D --> D1[完善 Schema 验证<br/>前端表单校验]
E --> E1[检查 ObjectId 格式<br/>校验参数类型]
| 错误 | 原因 | 解决方案 |
|---|---|---|
MongooseServerSelectionError: connect ECONNREFUSED |
MongoDB 未启动 | brew services start mongodb-community@7.0 |
MongoError: E11000 duplicate key error |
唯一索引冲突 | 检查字段值是否重复 |
ValidationError: Path 'email' is required |
必填字段缺失 | 补充必填字段 |
CastError: Cast to ObjectId failed |
_id 格式错误 | 检查 ObjectId 字符串格式 |
❓ 常见问题
mongoose.disconnect() 或 mongoose.connection.close(),确保所有连接优雅关闭。undefined。📖 小节
- deleteOne 删除单个文档,deleteMany 批量删除
- findOneAndDelete 原子返回被删除文档,适合队列/任务领取
- drop 删除集合,dropDatabase 删除数据库
- mongoose connect 连接 MongoDB,支持 Atlas 和本地
- Schema 定义数据类型、验证、默认值、索引
- Model CRUD:create / find / findOne / findByIdAndUpdate / findByIdAndDelete
- mongoose 文档方法:save() / lean() / populate() / toJSON()
📝 作业
- 基础题(⭐):用 deleteOne 删除 sku 为 'TEST-001' 的商品。
- 基础题(⭐):用 mongoose Schema 定义 Product 模型(含 sku、title、price、category、stock),执行 create + find + update + delete。
- 进阶题(⭐⭐):编写 Node.js 脚本,定期清理 30 天前的 expired orders(用 deleteMany)。
- 进阶题(⭐⭐):实现消息队列功能:用 findOneAndDelete + sort: { createdAt: 1 } 实现 FIFO 消费。
- 挑战题(⭐⭐⭐):用 mongoose + Schema + 中间件实现用户注册功能(含 email 唯一性校验、密码 bcrypt 哈希、创建时间戳)。