Node.js Patch请求数据更新滞后、返回旧数据问题排查
接口Patch操作异常问题
问题场景
向接口/inventorys/users/:inventoryId连续发送4次Patch请求,初始状态已手动清空inventory的users数组,预期实现:重复请求同一用户ID时,在添加/移除状态间切换,4次请求后users数组应为空。
请求详情
REQUEST1 => { users: 65ef2d09ef674c1e1a3632e5 } REQUEST2 => { users: 65ef2d09ef674c1e1a3632e5 } REQUEST3 => { users: 65ef2d11ef674c1e1a3632e9 } REQUEST4 => { users: 65ef2d11ef674c1e1a3632e9 }
接口处理代码
router.patch("/inventorys/users/:inventoryId", auth, async (req, res) => { try { //check if inventory exist and const inventory = await Inventory.findOne({ _id: req.params.inventoryId }); if (!inventory) { return res.status(404).send({ error: "Inventory not found" }); } // check if user exist in the data base. const newUserOrOldUser = await User.findOne({ _id: req.body.users, }); // Check if user already exist users array const userIndex = inventory.users.findIndex((userId) => userId.equals(req.body.users) ); // new user if (userIndex === -1) { await Inventory.updateOne( { _id: inventory._id }, { $push: { users: req.body.users } } ); console.log("add user", req.body.users, req.user._id); } else { await Inventory.updateOne( { _id: inventory._id }, { $pull: { users: req.body.users } } ); console.log("remove user", req.body.users); } return res.status(200).send(inventory); } catch (err) { if (err) { res.status(400).send({ error: err.message }); } } });
控制台输出
remove user 65ef2d09ef674c1e1a3632e5 add user 65ef2d09ef674c1e1a3632e5 add user 65ef2d11ef674c1e1a3632e9 remove user 65ef2d11ef674c1e1a3632e9
异常现象
- 第一次请求日志显示移除用户,但返回的响应数据中用户已被添加;
- 第二次请求日志显示添加用户,但返回的users数组为空;
- 第三次请求日志显示添加用户
65ef2d11ef674c1e1a3632e9,但返回的数组中存在65ef2d09ef674c1e1a3632e5; - 第四次请求日志显示移除用户
65ef2d11ef674c1e1a3632e9,但实际该用户尚未被添加,执行的是添加操作; - 最终4次请求后,users数组中有2个用户,与预期为空不符。
问题原因
核心问题是数据读取与更新的不一致性:
- 代码先通过
Inventory.findOne获取内存中的inventory对象,基于旧数据判断用户是否存在; - 执行
updateOne修改数据库后,未重新查询最新数据,直接返回初始的旧对象; - 连续请求时,后续请求可能读取到数据库更新前的旧数据,导致判断逻辑完全错误,出现日志、实际操作、响应数据三者不匹配的情况;
- 变量
newUserOrOldUser定义后未被使用,属于冗余代码。
修复方案
方案一:更新后重新查询最新数据返回
修改代码,在执行更新操作后,重新查询数据库获取最新的inventory对象再返回:
router.patch("/inventorys/users/:inventoryId", auth, async (req, res) => { try { const inventory = await Inventory.findOne({ _id: req.params.inventoryId }); if (!inventory) { return res.status(404).send({ error: "Inventory not found" }); } // 验证用户存在(可根据需求添加不存在时的错误返回) const user = await User.findOne({ _id: req.body.users }); if (!user) { return res.status(404).send({ error: "User not found" }); } const userId = req.body.users; const userIndex = inventory.users.findIndex(id => id.equals(userId)); if (userIndex === -1) { await Inventory.updateOne( { _id: inventory._id }, { $push: { users: userId } } ); console.log("add user", userId, req.user._id); } else { await Inventory.updateOne( { _id: inventory._id }, { $pull: { users: userId } } ); console.log("remove user", userId); } // 重新查询最新数据返回 const updatedInventory = await Inventory.findOne({ _id: inventory._id }); return res.status(200).send(updatedInventory); } catch (err) { res.status(400).send({ error: err.message }); } });
方案二:使用原子操作(高效避免并发问题)
利用MongoDB的原子操作,直接在数据库层面完成判断与更新,仅需一次数据库交互:
router.patch("/inventorys/users/:inventoryId", auth, async (req, res) => { try { const userId = req.body.users; // 验证用户存在 const userExists = await User.exists({ _id: userId }); if (!userExists) { return res.status(404).send({ error: "User not found" }); } // 原子操作:判断用户是否存在,自动执行添加/移除 const updatedInventory = await Inventory.findOneAndUpdate( { _id: req.params.inventoryId }, [ { $set: { users: { $cond: { if: { $in: [userId, "$users"] }, then: { $pull: ["$users", userId] }, else: { $addToSet: ["$users", userId] } } } } } ], { new: true, runValidators: true } ); if (!updatedInventory) { return res.status(404).send({ error: "Inventory not found" }); } // 打印对应操作日志 const userWasAdded = updatedInventory.users.some(id => id.equals(userId)); if (userWasAdded) { console.log("add user", userId, req.user._id); } else { console.log("remove user", userId); } return res.status(200).send(updatedInventory); } catch (err) { res.status(400).send({ error: err.message }); } });
内容的提问来源于stack exchange,提问作者Khant Min Si Thu
相关产品推荐
相关产品推荐

