【问题标题】:ATOMICally update multiple documents AND return them以原子方式更新多个文档并返回它们
【发布时间】:2016-12-10 02:39:30
【问题描述】:

在MongoDB 中,我正在寻找一种方法来自动更新多个文档并在一次调用中返回所有更新的文档。

我们可以在MongoDB做以下所有事情:

  • 原子更新一个文档并返回更新后的文档:findAndModify或findOneAndUpdate
  • 自动更新多个文档:update(...{multi: true} 或 updateMany
  • 查询并返回多个文档:find

我不喜欢一种方法来更新多个文档并在一次调用中将它们全部返回。有办法吗?我使用Mongoose作为查询包。

【问题讨论】:

    标签: mongodb mongoose atomic


    【解决方案1】:

    以原子方式更新多个文档:update(...{multi: true} 或 updateMany

    毫无疑问这是错误的:

    In MongoDB, write operations, e.g. db.collection.update(), db.collection.findAndModify(), db.collection.remove(), are atomic on the level of a single document.


    In MongoDB, a write operation is atomic on the level of a single document, even if the operation modifies multiple embedded documents within a single document.

    但是,您可以通过"using a two-phase commit approach" 模拟事务以原子方式更新多个文档,那里有详细描述。

    您还可以查看$isolated 运算符,其中"prevents a write operation that affects multiple documents from yielding to other reads or writes once the first document is written" 但它"does not provide “all-or-nothing” atomicity for write operations"

    总而言之,在 mongodb 级别(也不是驱动程序)是不可能的,但您可以在应用程序级别对其进行模拟,从而返回您需要的内容。

    【讨论】:

    • 感谢您在原子级别上的提醒。根据您发送的超级有用的链接,我认为 $isolated 运算符将在这种情况下提供我需要的更新。类似于您提到的两阶段提交的基于时间戳的方法应该可以确保我返回的文档与刚刚更新的文档相同。谢谢!
    【解决方案2】:

    MongoDB v3.6 引入了会话,这使得这成为可能:https://docs.mongodb.com/manual/reference/method/Session/。我使用猫鼬,这使得使用它们变得非常简单。这样的事情可能适用于您的情况:

    const session = await mongoose.startSession();
    
    // NOTE `withTransaction` expects a promise so I'm using `async`
    return session.withTransaction(async () => {
      await SomeModel.update({
        foo: "bar"
      }, {
        $set: {
          quux:"baz"
        }
      }).session(session);
    
      return SomeModel.find({
        foo: bar
      }).session(session);
    });
    

    【讨论】:

      【解决方案3】:

      我测试了 updateMany。

      测试1:

      使用 updateMany (pull) 更新 40K 文档,在执行过程中,突然关闭 db,然后一些节点通过(数据被拉出),一些失败(数据未在树中的某些级别 5 节点中拉出),重新启动 db 并再次运行 updateMany ,所有通过并且所有数据现在都是正确的。

      测试2:

      在字段上创建唯一索引,插入一些数据,在updateMany方法中,一些文档会因为唯一键冲突而失败。

      我的 test2 结果是:零文档已更新。


      function insertData() {
        const dataSource = app.models.Entity.getDataSource();
        return new Promise((resolve, reject) => {
          dataSource.connector.connect((err, db) => {
            if (err) {
              reject(new Error('.... error'));
              return;
            }
            const entityCollection = db.collection('Entity');
            // Create index
            entityCollection.createIndex({ age: 1 }, { unique: true })
            .then(() => {
              // Insert data
              const data = [
                {
                  id: uuid.v4(),
                  age: 1,
                  type: 'test',
                },
                {
                  id: uuid.v4(),
                  age: 2,
                  type: 'test',
                },
                {
                  id: uuid.v4(),
                  age: 3,
                  type: 'test',
                },
                {
                  id: uuid.v4(),
                  age: 4,
                  type: 'test',
                },
                {
                  id: uuid.v4(),
                  age: 5,
                  type: 'test',
                },
                {
                  id: uuid.v4(),
                  age: 6,
                  type: 'test',
                },
              ];
              return insertData(data);
            })
            .then(() => {
              resolve();
            })
            .catch((err2) => {
              reject(err2);
            });
          });
        });
      }
      
      function updateAge() {
        const dataSource = app.models.Entity.getDataSource();
        return new Promise((resolve, reject) => {
          dataSource.connector.connect((err, db) => {
            if (err) {
              reject(new Error('...error'));
              return;
            }
            const entityCollection = db.collection('Entity');
            entityCollection.updateMany(
              { age: { $gt: 0 } },
              { $mul: { age: 2 } },
            ).then(() => {
              resolve();
            })
            .catch((err2) => {
              logger.error(`ERROR is ${err2}`);
              reject(err2);
            });
          });
        });
      }
      

      测试结果是:零个文档被更新。 "msg":"ERROR is MongoError: E11000 duplicate key error collection: content-base.Entity index: age_1 dup key: { : 2 }","v":1} 1) updateMany 测试

      0 通过 (114ms) 1 次失败

      【讨论】:

        猜你喜欢
        • 2018-03-02
        • 2014-12-06
        • 1970-01-01
        • 2017-07-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多