MongoDB: 删除文档与Node.js入门

最后更新:2026-08-26

删除文档是数据清理的核心操作——本课程同时引入 Node.js + mongoose 实战。

掌握 deleteOne/deleteMany、Node.js + mongoose 的连接、Schema 定义、Model CRUD。

1. 你将学到


2. 一个全栈工程师的真实故事

(1) 痛点:清理过期数据需要复杂脚本

Bob 需要定期清理电商平台的过期订单和已注销用户:

JAVASCRIPT
// ❌ 反例:多次查询再删除(慢 + 竞态)
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 批量删除的解法

JAVASCRIPT
// ✅ 正例:一次性批量删除
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);

100%
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 确认。整个操作对单文档是原子的。删除后磁盘空间不会立即释放,而是标记为可重用空间。

100%
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)
JAVASCRIPT
// === deleteOne 基本用法 ===
db.products.deleteOne({ sku: 'PHONE-001' });

// 返回结果:
// { acknowledged: true, deletedCount: 1 }
返回字段 含义
acknowledged 是否已确认
deletedCount 删除的文档数(0 或 1)

▶ 示例 1:deleteOne 实战

JAVASCRIPT
// === 删除指定 _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

输出:

TEXT 📖 仅展示
// mongoose 操作成功执行
// 数据库查询/更新结果

4. deleteMany 批量删除

概念说明deleteMany 根据过滤条件删除所有匹配的文档,是批量数据清理的核心方法。与 deleteOne 只删除第一个匹配不同,deleteMany 可以一次删除成千上万条文档。常见场景包括:清理过期日志、删除已注销用户数据、移除测试数据等。

工作原理deleteMany 先查询所有匹配文档,再逐个删除。删除过程不是事务性的——如果中途失败,已删除的文档不会恢复。大批量删除建议分批执行,避免长时间锁定集合。

维度 deleteOne deleteMany
匹配范围 第一个匹配 所有匹配
删除数量 0 或 1 0 到 N
适用场景 精确删除单条 批量清理
风险 较高(误操作影响大)
JAVASCRIPT
// === 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 实战

JAVASCRIPT
// === 清理 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 集合的所有文档

输出:

TEXT 📖 仅展示
// 执行成功

5. findOneAndDelete 原子返回

概念说明findOneAndDelete 是一种特殊的删除方法,它在删除文档的同时返回被删除的文档内容。这解决了"先查后删"的竞态条件问题——传统做法需要先 findOne 获取文档、再 deleteOne 删除,两步之间文档可能被其他操作修改或删除。findOneAndDelete 将查询和删除合并为一次原子操作。

工作原理findOneAndDelete 在文档级别执行原子操作:找到匹配文档 → 记录文档内容 → 删除文档 → 返回记录的内容。默认返回删除前的文档状态,可通过 projection 选项控制返回字段。

100%
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 选项实现有序消费
适用场景 队列任务、消息消费、库存扣减
JAVASCRIPT
// === 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 实战

JAVASCRIPT
// === 场景:消息队列(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 } }
);

输出:

TEXT 📖 仅展示
// mongoose 操作成功执行
// 数据库查询/更新结果

6. drop 集合与 dropDatabase

概念说明dropdropDatabase 是最彻底的删除操作——drop 删除整个集合(包括所有文档和索引),dropDatabase 删除整个数据库(包括所有集合)。与 deleteMany({}) 不同,drop 操作不仅删除数据,还删除集合的元数据(索引定义、Schema 验证规则、capped 设置等)。

对比分析

维度 deleteMany({}) drop() dropDatabase()
删除范围 集合内所有文档 整个集合 整个数据库
保留索引 ✅ 保留 ❌ 全部删除 ❌ 全部删除
保留 capped 设置 ✅ 保留 ❌ 删除 ❌ 删除
保留 Schema 验证 ✅ 保留 ❌ 删除 ❌ 删除
速度 慢(逐条删除) 快(直接释放空间)
可恢复性 可通过 oplog 恢复 极难恢复 极难恢复
JAVASCRIPT
// === 删除集合 ===
db.products.drop();
// true(成功)或 false(集合不存在)

// === 删除数据库 ===
db.dropDatabase();
// { "dropped" : "shopdb", "ok" : 1 }

// === 慎用:删除整个集合的所有数据但保留集合 ===
db.products.deleteMany({});
// 等效但保留集合结构(索引、capped 设置)

▶ 示例 4:drop vs deleteMany 选择

JAVASCRIPT
// 场景:临时测试集合清理
// ✅ 推荐:drop(删除集合+索引,干净)
db.test_results.drop();

// ✅ 保留集合结构:deleteMany(仅清空文档)
db.user_sessions.deleteMany({});

输出:

TEXT 📖 仅展示
// 执行成功

7. Node.js + mongoose 连接 MongoDB

概念说明:mongoose 是 Node.js 生态最流行的 MongoDB ODM(Object Document Modeling),提供 Schema 定义、数据校验、中间件、关联查询等高级功能。本节从连接 MongoDB 开始,逐步引入 mongoose 的核心概念。

工作原理:mongoose 连接 MongoDB 的过程分为:创建连接实例 → 建立 TCP 连接 → 认证(如需)→ 选择数据库 → 连接池初始化 → 触发 connected 事件。mongoose 默认维护一个连接池(默认 5-100 个连接),复用连接避免频繁建立/断开 TCP 连接。

100%
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

BASH
npm install mongoose --save

(2) 连接 MongoDB

JAVASCRIPT
// === 基本连接 ===
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 控制最大并发数——设置过小会导致请求排队等待,设置过大会占用过多服务端资源。

JAVASCRIPT
// === 完整连接配置 ===
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

JAVASCRIPT
// === 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:完整连接管理

JAVASCRIPT
// 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 };

输出:

TEXT 📖 仅展示
❌ 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/updatedAtstrict: true 忽略未声明字段,versionKey: false 去掉 __v 版本键。

100%
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

JAVASCRIPT
// === 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 类型

JAVASCRIPT
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 设计

JAVASCRIPT
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);

输出:

TEXT 📖 仅展示
// 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 复杂业务逻辑
100%
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 创建

JAVASCRIPT
// === 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 读取

JAVASCRIPT
// === 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 更新

JAVASCRIPT
// === 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 删除

JAVASCRIPT
// === 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 实战

JAVASCRIPT
// === 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$' } });

输出:

TEXT 📖 仅展示
// mongoose 操作成功执行
// 数据库查询/更新结果

10. 常见错误排查

概念说明:Node.js + mongoose 开发中最常见的四类错误:连接错误(ECONNREFUSED)、唯一索引冲突(E11000)、Schema 验证失败(ValidationError)和类型转换失败(CastError)。理解这些错误的根本原因和处理方式是 mongoose 开发的基本功。

错误处理策略:连接错误需要重试机制,唯一索引冲突需要 upsert 或前端校验,Schema 验证失败需要完善表单校验,CastError 需要检查 ObjectId 格式。所有错误都应在应用层捕获并返回友好的错误消息——不要将原始的 MongoDB 错误堆栈暴露给前端用户。

100%
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 字符串格式

❓ 常见问题

Q deleteOne 和 findOneAndDelete 哪个好?
A 需要返回被删除文档用 findOneAndDelete;仅删除用 deleteOne。两者性能相当。
Q mongoose 连接需要关闭吗?
A 需要。在进程退出时调用 mongoose.disconnect()mongoose.connection.close(),确保所有连接优雅关闭。
Q Schema 改了字段,数据库需要迁移吗?
A 不需要。mongoose Schema 是应用层定义,不影响数据库。MongoDB 是 schema-less 的,新字段会自动添加。但旧文档可能缺新字段,应用层要处理 undefined
Q lean() 有什么性能影响?
A lean() 跳过 mongoose 文档 hydrate,返回纯 JS 对象。性能提升 3-5x,但失去 mongoose 文档方法(save()、populate() 等)。适用于纯查询 API。
Q Model.create 和 new Model + save 区别?
A 两者都触发 Schema 验证和中间件。Model.create() 语法更简洁,new + save 更适合需要分步操作的场景。

📖 小节


📝 作业

  1. 基础题(⭐):用 deleteOne 删除 sku 为 'TEST-001' 的商品。
  2. 基础题(⭐):用 mongoose Schema 定义 Product 模型(含 sku、title、price、category、stock),执行 create + find + update + delete。
  3. 进阶题(⭐⭐):编写 Node.js 脚本,定期清理 30 天前的 expired orders(用 deleteMany)。
  4. 进阶题(⭐⭐):实现消息队列功能:用 findOneAndDelete + sort: { createdAt: 1 } 实现 FIFO 消费。
  5. 挑战题(⭐⭐⭐):用 mongoose + Schema + 中间件实现用户注册功能(含 email 唯一性校验、密码 bcrypt 哈希、创建时间戳)。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏