Azure Cosmos DB如何避免文档同时编辑?及函数并发编辑疑问
关于Azure Cosmos DB文档并发编辑的冲突问题与解决方案
你的场景里,默认情况下会遇到最后写入者覆盖的坑,哪怕两个Function修改的是完全独立的字段也逃不掉。我给你拆解下具体会发生什么,以及怎么解决:
先看默认行为下的问题
假设用户文档初始状态是:{ "userId": "123", "name": "Alice", "email": "alice@test.com" },对应的_etag是"v1"。
- Function 1读取这个文档,解析后把
name改成"Alice Smith",还没来得及写入; - 同时Function 2读取同一个
_etag="v1"的文档,把email改成"alice.smith@test.com",先完成了写入。这时候文档的_etag变成了"v2",内容是{ "userId": "123", "name": "Alice", "email": "alice.smith@test.com" }; - 接着Function 1完成修改,把基于
v1版本的文档写入——这时候Cosmos DB默认不会检查版本,直接覆盖,最终文档变成{ "userId": "123", "name": "Alice Smith", "email": "alice@test.com" }。
看到没?Function 2对email的修改被完全覆盖了,哪怕两个操作改的是不同字段。这就是因为没有并发控制,两个操作都基于旧的文档快照修改,后写入的会直接替换掉之前的所有内容。
怎么避免?用乐观并发控制(OCC)
Cosmos DB提供了乐观锁机制,核心就是利用文档的_etag属性,步骤很简单:
- 读取文档时获取
_etag:不管你用SDK还是REST API,读取文档都会返回_etag字段,这是文档当前版本的唯一标识,每次修改都会更新。 - 写入时带上版本校验:执行更新操作时,把之前拿到的
_etag作为校验条件传给Cosmos DB。- 如果用SDK(比如.NET),可以通过
ItemRequestOptions的IfMatchEtag属性设置; - 如果直接调用API,就设置
If-Match请求头为_etag的值。
- 如果用SDK(比如.NET),可以通过
- 处理冲突重试:如果这期间文档已经被其他操作修改,Cosmos DB会返回
412 Precondition Failed错误。这时候你的Azure Function需要捕获这个错误,重新读取最新版本的文档,重新做修改,然后再尝试写入。
举个.NET SDK的例子,大概是这样:
var container = cosmosClient.GetContainer("DatabaseName", "UserData"); // 1. 读取文档并获取ETag var response = await container.ReadItemAsync<UserData>("userId123", new PartitionKey("userId123")); var userDoc = response.Resource; var etag = response.ETag; // 2. 修改文档 userDoc.Name = "Alice Smith"; // 3. 带ETag校验写入 try { var updateOptions = new ItemRequestOptions { IfMatchEtag = etag }; await container.ReplaceItemAsync(userDoc, "userId123", new PartitionKey("userId123"), updateOptions); } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.PreconditionFailed) { // 4. 冲突处理:重新读取最新文档,重试修改逻辑 // 这里可以加重试次数限制,避免无限循环 }
额外优化建议
- 如果你的场景并发冲突特别频繁,除了乐观锁,还可以考虑拆分文档:把用户的不同属性拆分到独立的文档里(比如用户基本信息、偏好设置各存一个文档),这样修改不同属性时就不会操作同一个文档,从根源上减少冲突。
- 不要依赖悲观锁:Cosmos DB没有提供悲观锁机制(比如锁住文档不让其他操作读取),所以乐观锁是官方推荐的并发控制方式。
内容的提问来源于stack exchange,提问作者meds
相关产品推荐
相关产品推荐

