You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Mongoose与MongoDB中实现用户Top5分数存储的最优方案及原子操作问询

这是个很典型的用户Top N分数存储需求,你当前的思路已经摸到门道了,但有个核心问题需要解决:你现在的两步操作(先push排序,再判断长度pop)不是原子的,在高并发场景下很容易出现数据不一致(比如同时两个请求添加分数,导致最终分数列表长度超过5)。下面给你讲最佳实现方式,包括如何把整个操作做成原子操作:

核心思路:用MongoDB的原子Update操作完成所有步骤

MongoDB的$push操作支持同时结合$each、$sort和$slice三个修饰符,这三个操作会作为一个原子单元执行,完美满足你的需求:添加新分数→按分数降序排序→自动截断到前5个(移除最低分)。

修正后的完整代码

const maxScoresToStore = 5;
const scoreEntrySchema = new Schema({ 
  score: Number, 
  when: { type: Date, default: Date.now } 
});
const scoreSchema = new Schema({ 
  _userid: { type: Schema.Types.ObjectId, ref: 'Users' }, 
  username: { type: String, index: { unique: true } }, 
  scores: [scoreEntrySchema] 
});
const scoreModel = mongoose.model("Scores", scoreSchema);

exports.addUserScore = async (uid, uname, score) => {
  const query = { _userid: uid, username: uname };
  const update = {
    $push: {
      "scores": {
        $each: [{ score }], // Mongoose会自动填充when字段的默认值
        $sort: { score: -1 }, // 按分数从高到低排序
        $slice: maxScoresToStore // 只保留前5个最高分数,自动移除后面的低分
      }
    }
  };
  const options = { upsert: true, new: true, setDefaultsOnInsert: true };
  
  try {
    const updatedDoc = await scoreModel.findOneAndUpdate(query, update, options);
    return updatedDoc;
  } catch (error) {
    console.error("添加用户分数失败:", error);
    throw error; // 向上抛出错误让调用方处理
  }
};

代码说明

  1. 原子性保障:整个$push操作中的$each(添加新分数)、$sort(排序)、$slice(截断)是MongoDB服务器端原子执行的,不会被其他请求打断,彻底避免并发场景下的数组长度异常问题。
  2. 自动填充时间:因为scoreEntrySchema里的when字段设置了default: Date.now,所以Mongoose会自动为新添加的分数条目填充当前时间,不需要手动传入。
  3. Upsert逻辑:upsert: true确保如果用户还没有分数记录,会自动创建一条新记录,符合你的需求。
  4. Async/Await优化:用async/await代替原来的then链式调用,代码更清晰,错误处理也更直观。

为什么原来的实现有问题?

你原来的代码是先执行findOneAndUpdate完成push和排序,然后在回调里判断长度再执行$pop,这两步是分开的:

  • 如果在findOneAndUpdate完成后、$pop执行前,有另一个请求添加了新分数,就会导致数组长度超过5;
  • 高并发下甚至可能出现多个请求同时进入回调执行$pop,导致误删过多分数。

额外注意事项

  • 确保你的MongoDB版本≥2.6:$push的$sort和$slice修饰符是从2.6版本开始支持的;
  • 唯一索引处理:username字段设置了唯一索引,当upsert遇到重复username时会抛出错误,你可以在catch块里根据业务需求处理这种情况(比如提示用户重复,或者忽略);
  • 性能:这种单条update操作的性能非常高,即使是高并发场景也能轻松应对,因为MongoDB对单文档的update操作优化得很好。

内容的提问来源于stack exchange,提问作者xSooDx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:02:19