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

MongoDB迁移执行结果不一致,无报错但仅部分记录生效求助

迁移脚本执行结果不稳定问题

使用migrate@2.1.0与mongoose@8.5.1编写Schema更新的up迁移脚本时,出现执行结果不一致的情况:预期4条记录受影响,但实际仅1-3条生效,偶尔甚至无变更。添加控制台日志后仅能看到部分输出,全程无报错。脚本逻辑验证时结果符合预期,但实际运行不稳定。

原迁移脚本代码:

module.exports.up = async () => {
  try {
    // Fetch SpecType documents that have instructions array not empty
    const specTypes = await SpecType.find({ instructions: { $exists: true, $ne: [] } }).lean();

    for (const specType of specTypes) {
      console.log('specType', specType);
      const newInstructions = [];
      await Promise.all(
        specType.instructions.map(async instruction => {
          let existingInstruction = await Instruction.findOne({ text: instruction });

          if (!existingInstruction) {
            existingInstruction = await new Instruction({ text: instruction }).save();
          }

          newInstructions.push({
            id: existingInstruction._id,
            text: existingInstruction.text
          });

          return Promise.resolve();
        })
      );
      console.log('NEW', newInstructions);
      await SpecType.updateOne({ _id: specType._id }, { $set: { instructions: newInstructions } });
    }

    console.log('Migration completed successfully.');
  } catch (error) {
    console.error('An error occurred during migration:', error);
    throw error;
  }
};

问题根源

  1. 异步竞态导致数组填充不完整:在Promise.all的异步回调中直接修改外部的newInstructions数组,由于异步操作的执行顺序不确定,可能出现数组未完全填充就执行updateOne的情况,导致更新数据缺失。
  2. 未校验更新操作结果:没有检查updateOne的返回值,无法确认更新是否真正生效,掩盖了可能的更新失败场景。
  3. .lean()的潜在风险:虽然此处查询用.lean()不直接引发问题,但返回普通JS对象而非Mongoose文档,后续若有依赖文档实例的操作易出问题。

修复后的迁移脚本

module.exports.up = async () => {
  try {
    // 获取instructions非空的SpecType文档,保留Mongoose文档实例
    const specTypes = await SpecType.find({ instructions: { $exists: true, $ne: [] } });

    for (const specType of specTypes) {
      console.log('Processing specType ID:', specType._id);
      // 由Promise.all直接收集异步处理结果,避免竞态问题
      const newInstructions = await Promise.all(
        specType.instructions.map(async instructionText => {
          let existingInstruction = await Instruction.findOne({ text: instructionText });
          
          if (!existingInstruction) {
            // 使用Mongoose推荐的create方法创建文档
            existingInstruction = await Instruction.create({ text: instructionText });
          }
          
          return {
            id: existingInstruction._id,
            text: existingInstruction.text
          };
        })
      );
      
      console.log('Generated instructions for', specType._id, ':', newInstructions);
      // 执行更新并校验结果
      const updateResult = await SpecType.updateOne(
        { _id: specType._id }, 
        { $set: { instructions: newInstructions } }
      );
      
      if (updateResult.modifiedCount === 0) {
        console.warn('No changes applied to specType:', specType._id);
      }
    }

    console.log('Migration completed successfully.');
  } catch (error) {
    console.error('Migration failed:', error);
    throw error;
  }
};

关键改进点

  • 消除异步竞态:不再通过外部数组收集结果,而是让map中的异步函数直接返回处理后的对象,由Promise.all统一收集,确保newInstructions完全填充后再执行更新。
  • 使用Mongoose原生创建方法:用Instruction.create()替代手动实例化后调用save(),代码更简洁可靠。
  • 校验更新结果:通过updateResult.modifiedCount确认记录是否被修改,便于排查未生效的情况。
  • 移除.lean():保留Mongoose文档实例,避免后续操作的潜在风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 23:12:43