Node.js/MongoDB循环更新文档时仅递增一次的问题
问题分析与解决方案
你遇到的是异步并发导致的竞态条件问题。同步for循环里直接调用异步函数setRouteHeader,会一次性触发5次异步调用,这些调用几乎同时执行:
- 多个调用同时查询到同一个
offset值(初始为0) - 各自把
offset加1变成1,然后执行更新 - 结果就是多次更新都把
offset设为1,而非预期的依次递增
控制台出现的1、1、1、3、3,是后续又有多个调用同时查到错误更新后的offset值,重复了覆盖问题导致的。
方案一:控制异步调用顺序,避免并发
把同步循环改成等待每个setRouteHeader执行完毕后再启动下一次,确保同一时间只有一个实例操作数据库:
const routes = [{name: 'route1'}, {name: 'route2'}]; // 改成异步自执行函数,控制循环顺序 (async function() { for (let i = 0; i < 5; i++) { await setRouteHeader(i, routes); } })(); async function setRouteHeader(i, routes) { for (let route of routes) { let lastOffset = await Routes.findOne({name: route.name}); let lo = lastOffset.offset; lo++ console.log(lo) // 执行相关任务 // ... await Routes.updateOne({name: route.name}, {offset: lo}); } }
方案二:使用MongoDB原子更新(更推荐)
直接用MongoDB的$inc操作符做原子递增,不需要先查询再更新,从根源上消除竞态条件,同时代码更高效:
const routes = [{name: 'route1'}, {name: 'route2'}]; (async function() { for (let i = 0; i < 5; i++) { await setRouteHeader(i, routes); } })(); async function setRouteHeader(i, routes) { for (let route of routes) { // 原子递增offset,同时返回更新后的文档 let updatedDoc = await Routes.findOneAndUpdate( {name: route.name}, {$inc: {offset: 1}}, // 原子性递增1 {new: true} // 指定返回更新后的文档 ); console.log(updatedDoc.offset); // 执行相关任务 // ... } }
这个方案的优势在于:即使后续需要并发执行,$inc的原子性也能保证每次递增都是正确的,不会出现覆盖问题,还减少了一次数据库查询操作,性能更优。
内容的提问来源于stack exchange,提问作者Jalagui
相关产品推荐
相关产品推荐

