使用UpsertDocumentAsync触发Azure Cosmos DB唯一索引约束冲突问题
核心原因
Cosmos DB 的 UpsertDocumentAsync 方法默认仅以系统内置的id字段+分区键作为文档是否存在的匹配依据,不会自动识别你自定义的UniqueKey规则(此处为UniqueName字段约束)。
首次写入空容器时,没有重复数据,所以插入全部成功。第二次写入相同UniqueName的记录时,你传入的record对象没有携带首次写入时生成/赋值的id值,或是id与已存在的文档不匹配,Upsert会判定当前记录为新文档执行插入操作,就触发了你预设的UniqueName唯一键约束,抛出冲突异常。
可选解决方案
方案1:将UniqueName直接作为文档的id字段值
如果业务层面UniqueName本身就是全局唯一的,可以直接把UniqueName的值赋值给系统id字段,Upsert会自动根据id匹配已有文档执行更新,不会触发唯一键冲突,修改代码如下:
public async Task ManageItems([ActivityTrigger] string records, [CosmosDB( databaseName: "mydatabase", collectionName: "items", ConnectionStringSetting = "CosmosDbConnectionString")] DocumentClient client, ILogger log) { var collectionUri = UriFactory.CreateDocumentCollectionUri("mydatabase", "items"); foreach (var record in records) { // 新增逻辑:将UniqueName赋值给系统id,作为Upsert匹配依据 record.id = record.UniqueName; log.LogDebug($"Upserting itemNumber={record.UniqueName}"); await client.UpsertDocumentAsync(collectionUri, record); } }
方案2:预查询匹配UniqueName的文档获取id后再Upsert
如果业务不允许将UniqueName作为id,可以先根据UniqueName查询容器中已存在的文档,拿到已有文档的id后赋值给当前待写入的记录,再执行Upsert:
foreach (var record in records) { log.LogDebug($"Upserting itemNumber={record.UniqueName}"); // 查询匹配UniqueName的已有文档 var existingDoc = client.CreateDocumentQuery<YourDocumentType>(collectionUri) .Where(d => d.UniqueName == record.UniqueName) .AsEnumerable() .FirstOrDefault(); if (existingDoc != null) { // 赋值已有id,让Upsert识别为更新操作 record.id = existingDoc.id; } await client.UpsertDocumentAsync(collectionUri, record); }
注意:高并发场景下该方案需要搭配乐观锁(ETag)控制,否则仍可能出现冲突。
方案3:使用存储过程实现原子化自定义Upsert
如果需要保证操作原子性,可以编写Cosmos DB存储过程,在存储过程内部先根据UniqueName匹配文档,存在则更新、不存在则插入,避免并发场景下的冲突问题。
补充说明
如果你的容器配置了分区键,Upsert的匹配逻辑还需要校验分区键值,必须保证待写入记录的分区键值和已有文档的分区键值一致,否则仍会被判定为新文档触发插入操作。
内容的提问来源于stack exchange,提问作者mkcoding
相关产品推荐
相关产品推荐

