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

Async函数中Await未等待Promise完成致MongoDB数据异常问题

解决MongoDB异步遍历中的重复插入与更新混乱问题

问题代码

async function myFunction() {
   await pickCollection.deleteMany({}); // clear collection
   let voters = await voterCollection.find({}).toArray();
   voters.forEach(async (voter) => {
      for (let pick of voter.ballot) {
          let document = await pickCollection.find({id: pick.id});
          if (!document) {
              await pickCollection.insertOne(pick);
              continue;
          }

          await pickCollection.updateOne({id: pick.id}, ....); //increment pick's votersCount
      }
    }
}     

问题分析

  1. forEach不支持异步等待:forEach会立刻启动所有异步回调,不会等待前一个回调的异步操作完成,导致多个find/insertOne/updateOne同时执行,出现竞态条件——比如多个请求同时检查同一个pick不存在,随后都执行插入,造成重复文档。
  2. 错误的文档存在判断:pickCollection.find({id: pick.id})返回的是MongoDB游标对象,而非实际文档,因此!document的判断永远为false,代码逻辑本身存在错误(推测你实际代码中误用了find而非findOne,才会出现重复插入的情况)。

解决方案

方案1:用for...of替代forEach,确保异步顺序执行

for...of可以和await配合,保证每个异步操作完成后再进入下一次循环,彻底避免并行执行的竞态问题,同时修正文档判断的错误:

async function myFunction() {
   await pickCollection.deleteMany({}); // 清空集合
   let voters = await voterCollection.find({}).toArray();
   
   // 使用for...of遍历voters,确保每个voter的处理完成后再进行下一个
   for (const voter of voters) {
      for (let pick of voter.ballot) {
          // 用findOne获取单个文档,正确判断是否存在
          const document = await pickCollection.findOne({id: pick.id});
          if (!document) {
              // 插入新pick,初始化votersCount为1
              await pickCollection.insertOne({...pick, votersCount: 1});
              continue;
          }

          // 递增已存在pick的votersCount
          await pickCollection.updateOne(
              {id: pick.id},
              {$inc: {votersCount: 1}}
          );
      }
   }
}

方案2:使用原子操作findOneAndUpdate(更优)

直接用MongoDB的原子操作合并“检查存在-插入/更新”逻辑,无需手动判断,从根本上消除竞态条件,同时提升性能:

async function myFunction() {
   await pickCollection.deleteMany({}); // 清空集合
   let voters = await voterCollection.find({}).toArray();
   
   for (const voter of voters) {
      for (let pick of voter.ballot) {
          // upsert: true 表示不存在则插入,存在则更新
          await pickCollection.findOneAndUpdate(
              {id: pick.id},
              {$inc: {votersCount: 1}},
              {
                  upsert: true,
                  // 插入时设置初始值,确保votersCount从1开始
                  setOnInsert: { ...pick, votersCount: 1 }
              }
          );
      }
   }
}

关键说明

  • for...of是处理异步遍历的标准方式,能保证异步操作的顺序执行,避免forEach的并行回调问题。
  • 原子操作findOneAndUpdate由MongoDB保证操作的原子性,即使在多线程环境下也不会出现重复插入或更新混乱的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 10:43:12