使用map()更新MongoDB记录时出现部分更新缺失问题
问题根因
你对map方法的特性认知是对的:Array.map()本身不会等待传入的异步回调执行完成,之前用map没出现更新丢失只是场景巧合,不是写法正确。
具体原因拆解:
- 你写的map逻辑会在同步遍历阶段瞬间发起121个
nft.save()的异步数据库请求,但是没有任何代码等待这些请求全部执行完成、确认写入成功。 - 之前用map没丢数据,只是因为当时后续逻辑执行/进程退出速度足够慢,所有发起的异步请求刚好有时间跑完;本次出现大量更新失败,本质是竞态问题触发:要么瞬间发起的上百个请求打满了MongoDB连接池,部分请求排队超时被直接丢弃,要么是当前执行的接口/脚本在map遍历结束后立刻返回、触发进程回收,还没执行完的save操作直接被中断,只有最先发起的2个请求赶在中断前完成了落盘。
- 同时,没有被await接收的异步请求如果抛出错误,会成为未捕获的Promise异常,在未做全局异常监听的Node.js环境里会被静默吞掉,你看不到具体的报错信息,自然很难定位问题。
- 普通for循环里的
await会串行等待每一次save操作执行完成才进入下一轮循环,整个循环执行结束时所有更新都已经确认写入数据库,因此不会出现更新丢失。
推荐写法
不要用裸map不等待异步回调的不稳定写法,根据场景选以下方案即可:
- 必须逐条操作文档时,用
Promise.all等待所有异步任务执行完成
const nfts = await waxModel.find(); // 等待所有更新操作全部完成再往下执行 await Promise.all( nfts.map(async nft => { nft.trait_attribute = null; return nft.save(); }) );
注意:单次更新的文档量超过50时,不建议直接用无并发控制的
Promise.all,很容易打满数据库连接池,建议搭配并发控制工具把并发数限制在5-10区间。
- 全量/批量条件更新优先用MongoDB原生批量接口,性能远高于逐条save
如果你的需求就是把一批文档的某个字段统一更新,完全不需要先把所有文档查到内存再逐条save,直接调用updateMany单次请求即可完成更新,性能是逐条save的几十倍,也不会有异步竞态问题:
// 匹配集合内所有文档,统一设置trait_attribute为null await waxModel.updateMany( {}, { $set: { trait_attribute: null } } );
内容的提问来源于stack exchange,提问作者Đoàn Đức Bảo
相关产品推荐
相关产品推荐

