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 } } }
问题分析
forEach不支持异步等待:forEach会立刻启动所有异步回调,不会等待前一个回调的异步操作完成,导致多个find/insertOne/updateOne同时执行,出现竞态条件——比如多个请求同时检查同一个pick不存在,随后都执行插入,造成重复文档。- 错误的文档存在判断:
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
相关产品推荐
相关产品推荐

