在CosmosDB中检查文档存在并按结果插入的最优实现方案是什么
问题解答
结论
完全可以通过Cosmos DB存储过程实现该需求,不仅能将原来的2次数据库请求压缩为1次,还能天然规避原实现中查询+插入两次请求间隙可能出现的并发竞态问题,因为Cosmos DB存储过程在单个逻辑分区内的所有操作是原子执行的。
前提约束
要保证跟踪表的分区键设置为OrderID:Cosmos DB存储过程的执行范围限定在单个逻辑分区内,同个OrderID的所有跟踪记录落在同一分区是存储过程能正确执行判断+插入逻辑的基础。
存储过程示例代码(服务端JS)
function insertTrackingIfNotExists(orderId) { const collection = getContext().getCollection(); const collectionLink = collection.getSelfLink(); const response = getContext().getResponse(); // 1. 查询是否存在符合条件的已有记录 const query = { query: "SELECT * FROM c WHERE c.OrderID = @orderId AND (c.Status = 'Completed' OR c.Status = 'InProgress')", parameters: [{name: "@orderId", value: orderId}] }; const isAccepted = collection.queryDocuments( collectionLink, query, function (err, items) { if (err) throw err; // 已存在符合条件的记录,返回null if (items.length > 0) { response.setBody(null); return; } // 2. 不存在则新建跟踪记录 const newTracking = { TrackingID: GUID(), // 服务端生成唯一ID,也可根据业务规则自定义生成逻辑 OrderID: orderId, Status: "InProgress" // 可根据业务需要调整默认状态 }; collection.createDocument(collectionLink, newTracking, function (err, createdDoc) { if (err) throw err; // 返回新生成的TrackingID response.setBody(createdDoc.TrackingID); }); } ); if (!isAccepted) throw new Error("存储过程执行被拒绝,RU不足或请求过载"); }
客户端调用改造示例
public static async Task InsertTracking(string OrderID) { // 调用存储过程,仅需1次请求,分区键指定为当前OrderID var result = await cosmosContainer.Scripts.ExecuteStoredProcedureAsync<string>( "insertTrackingIfNotExists", // 你创建存储过程时定义的ID new PartitionKey(OrderID), // 必须传入对应OrderID作为分区键 new[] { OrderID } // 存储过程入参 ); string trackingID = result.Resource; if (trackingID != null) { NotifyNewTracking(trackingID); } }
额外优势
相比原两次请求的实现,存储过程方案还解决了并发场景下的重复插入问题:原逻辑中两个请求同时查询到无记录,会同时触发插入导致重复数据,存储过程的原子执行特性完全避免了该问题。
内容的提问来源于stack exchange,提问作者HobbyLobbyVS
相关产品推荐
相关产品推荐

