setInterval定时查询MongoDB扣减ShieldPoints到0未停止功能异常
问题根源
- 异步逻辑等待失效:
Array.prototype.forEach不会等待内部的async回调执行完成,导致上一轮的数据库更新操作还没落地,下一轮的find查询就已经触发,拿到的始终是未更新的旧数据,本地判断u.ShieldPoints <= 0的逻辑自然失效。 - 更新操作无校验兜底:
findOneAndUpdate没有添加ShieldPoints > 0的查询条件,即使本地判断出现偏差,数据库层面也不会拦截扣减操作,最终会出现扣到负数的情况。 - 非核心优化点:不需要在定时器内部重复引入模型,全局引入一次即可,也可以直接在查询阶段就过滤掉
ShieldPoints <= 0的用户,减少无效数据处理。
修复后代码
// 全局引入模型,避免重复加载 const ProfileModelS = require("../models/ProfileSchema"); function shield() { const runShieldTask = async () => { try { // 查询阶段直接过滤出ShieldPoints大于0的用户,减少无效遍历 const validUsers = await ProfileModelS.find({ ShieldPoints: { $gt: 0 } }); // 用for...of代替forEach,支持异步等待逻辑 for (const u of validUsers) { // 更新操作添加ShieldPoints>0的条件,数据库层面兜底避免超扣 await ProfileModelS.findOneAndUpdate( { userID: u.userID, ShieldPoints: { $gt: 0 } }, { $inc: { ShieldPoints: -1 } } ); } } catch (err) { console.error('护盾扣减任务执行出错:', err); } finally { // 本次所有操作执行完成后再触发下一轮任务,避免任务堆积 setTimeout(runShieldTask, 1000); } } // 启动首次任务 runShieldTask(); } module.exports = shield
改动说明
- 用递归
setTimeout代替原有的setInterval,保证上一轮所有扣减操作全部完成后再触发下一轮查询,不会读取到未更新的旧数据。 - 查询阶段直接过滤
ShieldPoints <= 0的用户,不需要处理无效数据。 - 用
for...of遍历用户列表,支持异步等待,确保每个更新操作执行完成后再进行下一个。 - 更新操作添加
ShieldPoints > 0的查询条件,数据库层面做兜底,绝对不会出现扣减到负数的情况。
内容的提问来源于stack exchange,提问作者Bhavana Lad
相关产品推荐
相关产品推荐

