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

高负载下MongoDB文档计数不准致写入超限问题排查

问题描述

我是StackOverflow新手,若问题表述有误请见谅。我有一个Node.js Web服务器应用,向本地运行的MongoDB副本集写入数据,希望当集合中文档数达到上限(10个)时停止写入。
我最初的实现是先调用countDocuments()获取文档数,若小于10则写入,否则拒绝。该函数在低负载下正常,但高负载时会突破文档数上限。
之后我修改了函数,在写入后循环查询直到确认文档已写入再resolve,此版本可正常工作。我想了解为何高负载下MongoDB返回的文档计数不准确?

初始函数代码

async add_Server_Info_To_Db() {
    return new Promise(async (resolve, reject) => {
        try {
            if (await client.db("db").collection("col").countDocuments() === 10) {
                reject("max reached");
            } else {
                const genid = await random_Things_Gen.id_Gen();
                await client.db("db").collection("col").insertOne({ _id: genid });
                resolve();
            };
        } catch (error) {
            reject(error);
        };
    });
};

修改后函数代码

async add_Server_Info_To_Db() {
    return new Promise(async (resolve, reject) => {
        try {
            if (await client.db("db").collection("col").countDocuments() === 10) {
                reject("max reached");
            } else {
                const genid = await random_Things_Gen.id_Gen();
                await client.db("db").collection("col").insertOne({ _id: genid });
                while (true) {
                    await client.db("db").collection("col").findOne({ _id: genid }).then(() => {
                        resolve();
                    });
                };
            };
        } catch (error) {
            reject(error);
        };
    });
};

问题解答

核心原因:竞态条件+非原子操作

你的初始代码逻辑是先查询计数,再执行写入,这两步操作不是原子性的——高负载下,多个请求会同时执行countDocuments(),此时集合文档数还没到10,所有请求都通过了检查,紧接着同时执行insertOne,最终就会插入超过10个文档,突破上限。

为什么修改后的代码能工作?

你修改后在写入后循环查询,直到确认自己插入的文档存在,本质是等待当前写入操作完全落盘并被主节点确认,后续的请求再执行countDocuments()时,就能读到这个新插入的文档,从而避免多个请求同时通过“计数小于10”的检查。不过这种方式属于“事后补救”,效率不高,极端情况下仍可能存在竞态。

更优的解决方案

要彻底解决这个问题,应该用原子操作保证“检查+写入”的一致性:

  1. 用findOneAndUpdate维护计数文档:单独建一个计数文档,每次插入前先原子性地递增计数,同时判断是否超过上限。示例:
async add_Server_Info_To_Db() {
  const col = client.db("db").collection("col");
  const counterCol = client.db("db").collection("counters");

  // 原子性更新计数并检查上限
  const result = await counterCol.findOneAndUpdate(
    { _id: "col_count" },
    { $inc: { count: 1 } },
    { upsert: true, returnDocument: "after" }
  );

  if (result.value.count > 10) {
    // 超过上限,回滚计数
    await counterCol.updateOne({ _id: "col_count" }, { $inc: { count: -1 } });
    reject("max reached");
    return;
  }

  // 计数合法,执行插入
  const genid = await random_Things_Gen.id_Gen();
  await col.insertOne({ _id: genid });
  resolve();
}
  1. 使用MongoDB事务:把“查询计数+插入文档”放在一个事务里,保证操作的原子性(适合副本集环境)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 08:35:24