首页 > 解决方案 > MongoDB中upsert期间的并发问题

问题描述

我想根据具有该 id 的文档是否已经存在,在 MongoDB 中插入或更新文档。

我的对象

long _Id                 // Generated by myself (requirement)
List<Products> Products

所需的插入行为

检查是否存在 id X 的商店对象。如果不?插入具有给定 ID 和新产品列表的对象。

所需的更新行为

检查是否存在 id X 的商店对象?如是?通过将新项目推送到数组来更新对象。

代码

我将 C# 与 Mongo Driver Nuget 包一起使用。我想出了以下代码:

public async Task<MongoCmdResult> CreateOrUpdate(ShopDocument shopDocument)
{
    var filterLibrary    = Builders<ShopDocument>.Filter;
    var filter           = filterLibrary.Eq(shop => shop._Id, shopDocument._Id)
            & filterLibrary.ElemMatch(shop => shop.Products, products => products.ProductId != shopDocument.Products[0].ProductId);
    var updateDefinition = Builders<ShopDocument>.Update.Push<ShopDocument.Products>(shop => shop.Products, shopDocument.Products[0]);
    var updateOptions    = new FindOneAndUpdateOptions<ShopDocument> { IsUpsert = true };
    return await collection.FindOneAndUpdateAsync(filter, updateDefinition, updateOptions);
}

问题

虽然这段代码一开始似乎完美地完成了这项工作,但当多次调用并行时它开始抛出一些异常:Command findAndModify failed: Non-unique id..

我对为什么并行执行(在此示例中为 2 个并行调用)时此(有时)失败的理论是,在两个调用(应用过滤器)中读取的集合期间,似乎没有具有给定 ID 的文档。MongoDB 决定两个查询都应该插入。第一个成功,第二个失败,Non-unique id因为另一个查询更快并且要插入的文档已经存在(基于 id)。

FindOneAndUpdateAsync在这种情况下,除了尝试捕获该方法并在出现重复键错误的情况下重试一次之外,没有其他(阅读:更好的)方法吗?例如,我可以在更新选项或过滤器中更改某些内容以解决此问题吗?

标签: c#mongodb

解决方案


除了在代码中捕获该错误并相应地处理它之外,我认为目前没有更好的方法。

您的理论是正确的,因为 2 个线程恰好同时插入。然而,这在并发系统中是预期的。

我发现解决这个问题的最佳方法是:

  1. 尝试插入
  2. 如果失败,请改为更新

这基本上是您在问题中描述的情况。

在即将发布的 MongoDB 4.2 中,这可以通过使用带有 upsert 选项的更新方法自动解决。这在SERVER-14322中有详细描述,如果可以检测到这样做是安全的,服务器将自动重试执行 upsert。


推荐阅读