Mongoose循环更新Owner文档balance字段仅生效一次的问题
问题分析:竞态条件导致的balance修改不生效
这是典型的**竞态条件(Race Condition)**问题,咱们一点点拆解原因:
你用forEach遍历数组时,会立刻触发所有的异步操作(不管是async/await还是.then()写法),这些操作几乎同时向数据库发起查询,拿到的都是同一个初始的balance值(99.53)。之后每个操作都基于这个初始值计算出99.52,再保存回数据库——相当于五次修改都是“原地踏步”,最后覆盖的结果自然是99.52,而不是叠加五次的99.48。
之前尝试的.then()写法本质和async/await在forEach里的问题一样:所有异步任务并行执行,没有等待前一次修改完成就发起下一次,完全没解决竞态问题。
解决方案
这里给你两种靠谱的解决思路,推荐第二种:
1. 用for...of替代forEach,保证串行执行
for...of会等待每个异步操作完成后再进入下一次循环,这样每次修改都是基于数据库中最新的balance值:
// 替换原来的forEach循环 for (const user of users) { if (user.active) { /* SOME STUFF */ const owner = await Owner.findById(user.owner); // 你的小数精度处理逻辑可以保留 owner.balance = (owner.balance * 100 - 0.01 * 100) / 100; await owner.save(); } }
这样循环会一步步执行:查最新值→修改→保存,再进入下一次,五次操作后就能得到99.48的正确结果。
2. 使用Mongoose原子更新操作(推荐)
直接在数据库层面执行修改,不需要先查询再保存,从根源上避免竞态,性能也更好:
for (const user of users) { if (user.active) { /* SOME STUFF */ // 用$inc原子操作直接在数据库中减去0.01 await Owner.updateOne( { _id: user.owner }, { $inc: { balance: -0.01 } } ); } }
如果担心浮点精度问题,更稳妥的做法是把balance存储为整数(比如用“分”作为单位,99.53元存为9953),每次操作减1,彻底规避浮点运算的精度误差:
// 假设balance存的是分,每次减1分(即0.01元) await Owner.updateOne( { _id: user.owner }, { $inc: { balance: -1 } } );
内容的提问来源于stack exchange,提问作者Denis Tolkachev
相关产品推荐
相关产品推荐

