You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Mongoose批量更新符合条件文档的最佳实践(结合node-cron)

批量更新Mongoose文档的最佳实践

问题场景

通过node-cron定时执行函数,从数据库获取所有Appointment文档,将满足「当前时间超过预约结束时间」条件的文档的active字段设为false,再批量更新回数据库。现有代码中,Appointment.find()返回Mongoose文档数组,无法直接调用.save()方法。

现有实现代码:

const Appointment = require("../models/Appointment");
const {
  getDate,
  getMinutes,
  getHours,
  getYear,
  getMonth,
  addMonths,
} = require("date-fns");

const retrieveAllAppointmentsCron = async () => {
  const date = new Date();
  const year = getYear(date);
  const month = getMonth(date) + 1;
  const day = getDate(date);
  const hour = getHours(date);
  const minutes = getMinutes(date);
  if (month < 10) month = "0" + month;
  if (day < 10) day = "0" + day;
  if (hour < 10) hour = "0" + hour;
  if (minutes < 10) minutes = "0" + minutes;

  const nowFormDate = new Date(`${year}-${month}-${day}T${hour}:${minutes}`);
  const res = await Appointment.find()
    .select("-shopId -customerName -service -comments -email ")
    .exec();
    let cancleCounter=0;
  res.map((app) => {
    var appFormEndDate = new Date(`${app.date}T${app.endTime}`);
    if(nowFormDate>appFormEndDate)
    {
        app.active = false;
        cancleCounter++;
    }
  });
  await res.save()
  console.log(`a total of ${cancleCounter} appointments where updated to completed`)
  cancleCounter=0;
};

module.exports = {
  retrieveAllAppointmentsCron,
};

最佳实践方案

方案1:使用updateMany直接在数据库层面完成(推荐)

这是性能最优的方案,无需将所有文档加载到内存,直接通过MongoDB的查询条件筛选目标文档并批量更新,减少网络IO和内存占用。

实现代码:

const retrieveAllAppointmentsCron = async () => {
  const nowFormDate = new Date(); // 按业务逻辑生成当前时间
  // 批量更新符合条件的文档
  const updateResult = await Appointment.updateMany(
    {
      // 数据库层面拼接日期与时间,判断是否早于当前时间
      $expr: {
        $lt: [
          { $dateFromString: { dateString: { $concat: ["$date", "T", "$endTime"] } } },
          nowFormDate
        ]
      },
      active: true // 只更新未标记为非活跃的文档,避免重复操作
    },
    { $set: { active: false } } // 执行字段更新
  );

  console.log(`a total of ${updateResult.modifiedCount} appointments were updated to completed`);
};

逻辑解释:

  • updateMany接收两个核心参数:查询条件和更新操作
  • 通过$expr利用MongoDB聚合表达式,直接在数据库层完成日期拼接与比较,无需把数据拉到本地处理
  • 增加active: true的过滤条件,避免重复更新已处理过的文档
  • 返回的modifiedCount直接对应实际更新的文档数量,无需手动计数

方案2:遍历文档数组逐个调用save(适用于复杂内存处理场景)

如果必须将文档加载到内存做额外业务逻辑处理,可以遍历每个文档实例,调用其自身的.save()方法,用Promise.all并行处理提升效率。

实现代码:

const retrieveAllAppointmentsCron = async () => {
  const nowFormDate = new Date(); // 按业务逻辑生成当前时间
  const res = await Appointment.find()
    .select("-shopId -customerName -service -comments -email ")
    .exec();
  
  const updatePromises = [];
  let cancelCounter = 0;

  res.forEach((app) => {
    const appFormEndDate = new Date(`${app.date}T${app.endTime}`);
    if (nowFormDate > appFormEndDate && app.active) {
      app.active = false;
      updatePromises.push(app.save());
      cancelCounter++;
    }
  });

  await Promise.all(updatePromises); // 并行执行所有保存操作
  console.log(`a total of ${cancelCounter} appointments were updated to completed`);
};

逻辑解释:

  • Mongoose的单个文档实例自带.save()方法,但数组本身没有该方法,因此需要遍历每个元素单独调用
  • 收集所有待保存的Promise,用Promise.all并行执行,相比逐个await能大幅节省执行时间
  • 增加app.active判断,避免重复保存已修改过的文档

方案3:使用lean()获取普通JS对象,再用bulkWrite批量更新

如果不需要Mongoose文档的实例方法,可通过lean()将查询结果转为普通JavaScript对象,修改后用bulkWrite批量提交更新操作。

实现代码:

const retrieveAllAppointmentsCron = async () => {
  const nowFormDate = new Date(); // 按业务逻辑生成当前时间
  const res = await Appointment.find()
    .select("-shopId -customerName -service -comments -email ")
    .lean()
    .exec();
  
  // 生成批量更新操作数组
  const updateOperations = res
    .filter(app => {
      const appFormEndDate = new Date(`${app.date}T${app.endTime}`);
      return nowFormDate > appFormEndDate && app.active;
    })
    .map(app => ({
      updateOne: {
        filter: { _id: app._id },
        update: { $set: { active: false } }
      }
    }));

  const bulkResult = await Appointment.bulkWrite(updateOperations);
  console.log(`a total of ${bulkResult.modifiedCount} appointments were updated to completed`);
};

逻辑解释:

  • lean()返回普通JS对象,内存占用更小、处理速度更快
  • 先筛选出需要更新的文档,再生成bulkWrite要求的操作格式数组
  • bulkWrite可一次性执行多个更新操作,减少数据库请求次数

内容的提问来源于stack exchange,提问作者Kwstas Greco

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 23:55:15