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

Mongoose如何处理异步并行修改操作?

问题:Mongoose并行保存文档时的冲突处理

我正在使用Node.js和MongoDB(借助Mongoose包)进行开发。编写了一个用于更新文档的异步函数,当前需要并行执行10次该函数并传入不同参数。我的疑问是:若文档已被提前保存,Mongoose/MongoDB会在保存前自动重新获取文档吗?还是会抛出错误?

以下是我的代码:

const crawlSitemap = async (projectId) => {
  try {
    const project = await Project.findById(projectId);
    const browser = await puppeteer.launch({ headless: "new" });

    const crawl = (sitemap) => async () => {
      const page = await browser.newPage();
      page.setDefaultNavigationTimeout(120 * 1000);
      console.log(sitemap.url);
      if (!validator.isURL(sitemap.url) || sitemap.crawlStatus !== "queued")
        return;
      await page.goto(sitemap.url, { waitUntil: "networkidle0" });
      await page.evaluate(() => {
        const scripts = document.querySelectorAll("script");
        scripts.forEach((script) => script.remove());

        const images = document.querySelectorAll("img");
        images.forEach((img) => img.remove());

        const styles = document.querySelectorAll(
          'link[rel="stylesheet"], style'
        );
        styles.forEach((style) => style.remove());
      });
      const textContent = await page.evaluate(() => document.body.textContent);
      await page.close();

      await promisify(fs.writeFile)(
        path.join(__dirname, `tmp/${sitemap.url.replaceAll("/", "-")}.txt`),
        textContent,
        "utf-8"
      );
      sitemap.crawlStatus = "ok";
      await project.save();
    };

    await parallelLimit(project.sitemap.map(crawl), 10);

    await browser.close();
  } catch (error) {
    console.log(error);
  }
};

回答

Mongoose默认不会自动重新获取文档,你当前的写法大概率会遇到更新丢失或者版本冲突错误,原因如下:

  • 函数开头就把project文档从数据库拉到了内存中,所有并行的crawl任务都是在修改同一个内存对象的sitemap数组项。
  • 当第一个并行任务修改完sitemap.crawlStatus并调用project.save()后,数据库里的文档版本(默认通过__v字段标识)已经更新,但其他并行任务手里的还是旧的内存对象。
  • 后续任务再调用project.save()时,如果Schema开启了版本校验(默认开启),会直接抛出版本冲突错误;如果没开版本校验,新的保存会直接覆盖数据库里的最新数据,导致前面任务的修改丢失。

推荐解决方案:使用原子更新

不要先拉取整个文档到内存,而是直接用Mongoose的原子更新方法,单独修改目标sitemap项。这样每次更新都是直接操作数据库,完全避免并行冲突问题:

// 替换原代码中的await project.save()
await Project.updateOne(
  { 
    _id: projectId, 
    'sitemap._id': sitemap._id // 定位到具体的sitemap子文档
  },
  { 'sitemap.$.crawlStatus': 'ok' } // 更新该子文档的状态
);

其他可选方案(不推荐)

  • 每次保存前重新获取最新的project文档:但并行场景下还是可能出现冲突,且频繁查询会降低性能。
  • 实现分布式锁:复杂度高,增加系统维护成本,没必要为这个场景引入。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 18:33:24