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

Mongoose查询结果修改异常:单条修改致所有文档字段变更

问题

我实现了如下异步函数:

const getXNumberOfDocuments = async function getXNumberOfDocuments(
  page,
  results_per_page
) {
  /*************************************************** */
  results_per_page = parseInt(results_per_page);
  let x_number_of_documents = await Document.find()
    .populate([
      {
        path: "user",
        populate: {
          path: "profile",
          select: ["profileImageURL"],
        },
      },
    ])
    .limit(results_per_page)
    .skip(results_per_page * page)
    .lean();
  /*************************************************** */
  // I will stop this loop at i=0 by throwing an error
  // As you se below
  for (let i = 0; i < x_number_of_documents.length; i++) {
    console.log("🚀 ~ file: documentsServices.js ~ line 295 ~ i", i);
    // #BUG:
    // For some reason this changes
    // profileImageURL for i=0 and i=1 as well
    x_number_of_documents[i].user.profile.profileImageURL =
      "THIS SHOULD ONLY BE MODIFIED FOR i=0 BUT IT IS MODIFIED FOR ALL OF THEM!";
    // await s3_config.getImageReadSignedUrl(
    //   x_number_of_documents[i].user.profile.profileImageURL
    // );

    /*************************************************** */
    // I check all documents here
    // And I find that profileImageURL was modified in all of them
    // Even though I stopped the loop at i=0
    for (let j = 0; j < x_number_of_documents.length; j++) {
      console.log(
        "🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀 j,",
        j
      );
      console.log(
        "🚀 ~ file: documentsServices.js ~ line 299 ~ x_number_of_documents[j].user.profile.profileImageURL",
        x_number_of_documents[j].user.profile.profileImageURL
      );
      console.log(
        "🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀 "
      );
    }
    /*************************************************** */
    throw new Error("STOPPING LOOP AT i=0");
    /*************************************************** */
  }
  return x_number_of_documents;
};

该函数通过Mongoose的find方法结合populate、limit、skip及lean()获取分页文档数据,正常返回包含多个带user.profile.profileImageURL字段的对象。当我尝试仅修改结果数组中索引为0的元素的profileImageURL字段时,发现所有元素的该字段都被修改;但直接修改user字段时仅索引0的元素变更,修改user.profile字段时又出现所有元素变更的情况。相关测试输出及简化场景如下:

x_number_of_documents[0].user.profile.profileImageURL =
  "THIS SHOULD ONLY BE MODIFIED FOR i=0 BUT IT IS MODIFIED FOR ALL OF THEM!";
console.log(
  "🚀 ~ file: documentsServices.js ~ line 308 ~ x_number_of_documents[0].user.profile.profileImageURL",
  x_number_of_documents[0].user.profile.profileImageURL
);
console.log(
  "🚀 ~ file: documentsServices.js ~ line 308 ~ x_number_of_documents[1].user.profile.profileImageURL",
  x_number_of_documents[1].user.profile.profileImageURL
);

测试结果显示所有元素的profileImageURL均被修改,我怀疑这与嵌入式文档的修改逻辑有关,请问这是什么原因导致的?

原因分析

这是因为多个文档指向了同一个profile对象的引用。

具体细节:

  • 当使用Mongoose的populate关联数据时,如果多条Document记录关联的是同一个User,且该User绑定的是同一个Profile,Mongoose默认会复用同一个内存对象引用,不会为每条Document创建独立的profile副本。
  • 即使调用了lean()返回普通JavaScript对象,对象引用的特性依然存在——所有关联该profile的Document元素,它们的user.profile本质是内存中的同一个对象。
  • 所以修改x_number_of_documents[0].user.profile.profileImageURL时,实际是修改了这个共享对象的属性,所有引用它的元素自然都会同步变化。
  • 直接修改user字段时,是给索引0的元素重新赋值了新对象,不会影响其他元素的user引用;但修改user.profile时,同样是操作共享的profile对象,因此所有元素都会受影响。

解决思路:如果需要独立修改每个元素的profile属性,在获取数据后为每个user.profile做深拷贝(比如用JSON.parse(JSON.stringify(...))或深拷贝工具函数),切断对象引用关系即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 06:20:24