阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 更新文档(Update)

更新文档(Update)

作者:陈川 阅读数:4661人阅读 分类: MongoDB

更新文档的基础方法

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') {
    // 处理版本冲突
  }
}

更新操作性能优化

对于大型集合的更新操作,可以考虑以下优化策略:

  1. 使用投影只返回必要的字段
await User.updateMany(
  { status: 'active' },
  { $set: { lastActive: new Date() } },
  { select: '_id status' }
);
  1. 添加适当的索引
userSchema.index({ status: 1 });
  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();
}

更新操作的最佳实践

  1. 总是使用操作符进行更新,避免直接替换整个文档
// 不推荐
await User.updateOne({ _id }, userData);

// 推荐
await User.updateOne(
  { _id },
  { $set: userData }
);
  1. 为频繁更新的字段使用适当的索引
  2. 考虑使用lean()提高只读操作的性能
const user = await User.findById(userId).lean();
  1. 监控慢查询
mongoose.set('debug', function(collectionName, method, query, doc) {
  logger.debug(`${collectionName}.${method}`, JSON.stringify(query));
});
  1. 处理更新可能不匹配任何文档的情况
const result = await User.updateOne(
  { _id: nonExistentId },
  { $set: { name: 'test' } }
);

if (result.matchedCount === 0) {
  // 处理未找到文档的情况
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌