更新文档(Update)
更新文档的基础方法
Mongoose提供了多种更新文档的方式,最基础的是updateOne()
和updateMany()
方法。updateOne()
更新匹配的第一个文档,而updateMany()
会更新所有匹配的文档。
// 更新单个文档
await User.updateOne(
{ name: '张三' },
{ $set: { age: 30 } }
);
// 更新多个文档
await User.updateMany(
{ status: 'active' },
{ $set: { status: 'inactive' } }
);
更新操作符$set
用于指定要更新的字段,其他常用操作符还包括:
$inc
:增加字段值$push
:向数组添加元素$pull
:从数组移除元素
使用findByIdAndUpdate
findByIdAndUpdate()
是更新文档的便捷方法,它通过ID查找并更新文档,默认返回更新前的文档。可以通过设置new: true
选项来返回更新后的文档。
const updatedUser = await User.findByIdAndUpdate(
'5f8d0d55b54764421b7156da',
{ $set: { name: '李四' } },
{ new: true }
);
这个方法特别适合需要获取更新后文档的场景,比如在响应中返回更新后的数据。
原子更新操作
Mongoose支持多种原子更新操作符,确保在并发环境下数据的一致性:
// 增加数值
await Product.updateOne(
{ _id: productId },
{ $inc: { stock: -1 } }
);
// 向数组添加元素
await BlogPost.updateOne(
{ _id: postId },
{ $push: { comments: newComment } }
);
// 从数组移除元素
await BlogPost.updateOne(
{ _id: postId },
{ $pull: { comments: { _id: commentId } } }
);
批量更新操作
对于需要更新大量文档的场景,可以使用批量写入操作提高性能:
const bulkOps = [
{
updateOne: {
filter: { status: 'pending' },
update: { $set: { status: 'processed' } }
}
},
{
updateMany: {
filter: { createdAt: { $lt: new Date('2023-01-01') } },
update: { $set: { archived: true } }
}
}
];
await User.bulkWrite(bulkOps);
更新验证
默认情况下,Mongoose在更新时会跳过验证。要启用验证,需要设置runValidators: true
选项:
await User.updateOne(
{ _id: userId },
{ $set: { email: 'invalid-email' } },
{ runValidators: true }
);
这会导致操作失败,因为电子邮件格式无效。还可以使用context
选项使验证器能够访问原始文档:
await User.updateOne(
{ _id: userId },
{ $set: { age: 17 } },
{
runValidators: true,
context: 'query'
}
);
中间件处理
Mongoose提供了更新操作的中间件钩子,可以在更新前后执行自定义逻辑:
schema.pre('updateOne', function(next) {
console.log('即将更新文档');
this.set({ updatedAt: new Date() });
next();
});
schema.post('updateOne', function(doc, next) {
console.log('文档已更新');
next();
});
乐观并发控制
使用版本号实现乐观锁,防止并发更新冲突:
const user = await User.findById(userId);
user.name = '王五';
await user.save();
如果在此期间文档被其他操作修改,save()操作会失败并抛出VersionError。可以捕获这个错误并处理冲突:
try {
await user.save();
} catch (err) {
if (err.name === 'VersionError') {
// 处理版本冲突
}
}
更新操作性能优化
对于大型集合的更新操作,可以考虑以下优化策略:
- 使用投影只返回必要的字段
await User.updateMany(
{ status: 'active' },
{ $set: { lastActive: new Date() } },
{ select: '_id status' }
);
- 添加适当的索引
userSchema.index({ status: 1 });
- 分批处理大量更新
const batchSize = 100;
let skip = 0;
let hasMore = true;
while (hasMore) {
const users = await User.find({})
.skip(skip)
.limit(batchSize);
if (users.length === 0) {
hasMore = false;
} else {
await User.updateMany(
{ _id: { $in: users.map(u => u._id) } },
{ $set: { processed: true } }
);
skip += batchSize;
}
}
事务中的更新操作
在需要保证多个更新操作原子性的场景下,可以使用MongoDB的事务:
const session = await mongoose.startSession();
session.startTransaction();
try {
await User.updateOne(
{ _id: userId },
{ $inc: { balance: -100 } },
{ session }
);
await Payment.create(
[{ userId, amount: 100 }],
{ session }
);
await session.commitTransaction();
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
session.endSession();
}
更新操作的最佳实践
- 总是使用操作符进行更新,避免直接替换整个文档
// 不推荐
await User.updateOne({ _id }, userData);
// 推荐
await User.updateOne(
{ _id },
{ $set: userData }
);
- 为频繁更新的字段使用适当的索引
- 考虑使用
lean()
提高只读操作的性能
const user = await User.findById(userId).lean();
- 监控慢查询
mongoose.set('debug', function(collectionName, method, query, doc) {
logger.debug(`${collectionName}.${method}`, JSON.stringify(query));
});
- 处理更新可能不匹配任何文档的情况
const result = await User.updateOne(
{ _id: nonExistentId },
{ $set: { name: 'test' } }
);
if (result.matchedCount === 0) {
// 处理未找到文档的情况
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:查询文档(Read)
下一篇:删除文档(Delete)