MongoDB“原子”查找实体,如果未找到,则更新另一个实体

tv6aics1  于 2023-03-01  发布在  Go
关注(0)|答案(1)|浏览(97)

我试图通过MongoDB操作解决争用条件问题,这样就可以避免实现锁。
我的目标是能够检查集合中是否有任何具有特定状态的文档,如果没有,则将该状态设置为我选择的集合中的现有文档。该目标来自于这样一种需要,即由于并发性,我尝试的任何实现都会使我在竞争条件下更新多个文档。
让我举一个平行的例子,在并发调用的场景中,我的目标是逻辑检查是否没有任何现有的卡具有ACTIVE状态,我选择的卡被设置为ACTIVE,如果已经有ACTIVE卡,让它保持原样。所有这些都在一个MongoDB操作中完成。这个操作是由一个不同的流触发的,所以我可以't在执行该逻辑之前,不要更改流程以将卡设置为ACTIVE。
我尝试使用findOneAndUpdate(甚至findOneAndReplace)操作:

const query = {
  userId: 'user-id-1',
  status: 'ACTIVE',
};

Card.findOneAndUpdate(
    query,
    {
        $set: {
            _id: 'chosen-id', // A certain chosen ID to update if not found.
            userId: 'user-id-1', 
            status: 'ACTIVE',
        },
    },
    {
        upsert: true,
        returnNewDocument: true,
    }
);

但我发现自己无法实现我的目标与任何可用的选项,因此我只实现更新找到的卡。如果没有找到,一切保持不变,我选择的卡没有设置为活动。唯一的情况是当添加像在示例upsert: true我可以创建一个新的文档,但我不想,因为我想更新一个不同的现有文档。
文档示例:

{ "_id": "card-id-1", "userId": "user-id-1", "status": "READY" }
{ "_id": "card-id-2", "userId": "user-id-1", "status": "CANCELLED" }
{ "_id": "card-id-3", "userId": "user-id-1", "status": "READY" }
{ "_id": "card-id-4", "userId": "user-id-1", "status": "READY" }

在这种情况下,如果执行并选择card-id-4作为回退(如果未找到ACTIVE),则预期结果应为:

{ "_id": "card-id-1", "userId": "user-id-1", "status": "READY" }
{ "_id": "card-id-2", "userId": "user-id-1", "status": "CANCELLED" }
{ "_id": "card-id-3", "userId": "user-id-1", "status": "READY" }
{ "_id": "card-id-4", "userId": "user-id-1", "status": "ACTIVE" }

如果再次执行,集合应该保持不变,因为card-id-4是ACTIVE,所以操作正在查找一个集合。
MongoDB是否有任何实现可以在一次操作中实现这种行为?无论版本如何。如果需要,我对升级没有限制。

qxgroojn

qxgroojn1#

您可以在聚合管道中执行以下操作:

  1. $facet检查是否存在ACTIVE记录
  • 取决于结果:
  • 如果找到,将_id设置为活动记录的_id
  • 如果未找到,则将_id设置为回退_id
  • 使用_id将构造的更新对象返回到集合

默认情况下,单个文档级别上的操作是原子的,因此,您不需要太担心争用条件。

db.collection.aggregate([
  {
    "$facet": {
      "hasActive": [
        {
          $match: {
            userId: "user-id-1",
            status: "ACTIVE"
          }
        },
        {
          "$limit": 1
        }
      ]
    }
  },
  {
    "$project": {
      _id: {
        $ifNull: [
          {
            $first: "$hasActive._id"
          },
          "card-id-4"// chosen fallback id
          
        ]
      },
      userId: "user-id-1",
      status: "ACTIVE"
    }
  },
  {
    "$merge": {
      "into": "collection",
      "on": "_id",
      "whenMatched": "merge"
    }
  }
])

Mongo Playground when no active
Mongo Playground when there is active

相关问题