You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Cosmos DB存储过程竞态问题:强一致性下重复创建文档求助

Cosmos DB存储过程竞态问题解决方案

问题根源分析

你遇到的重复创建问题,核心是现有代码存在语法错误、逻辑判断颠倒,且未利用Cosmos DB的原生唯一性约束,即使强一致性和分区串行执行的机制也无法抵消这些错误:

  • 查询语句缺少AND连接条件,导致无法正确匹配已存在的文档
  • 逻辑判断完全颠倒:文档存在时反而执行创建操作,不存在时不创建
  • 未将业务唯一标识(currentFilter)设为文档id,无法触发Cosmos DB的同一分区内id唯一性校验

修复步骤与代码示例

1. 修复查询语句与逻辑判断

首先修正查询的语法错误,改用参数化查询避免SQL注入,同时调整判断逻辑:

async function checkIfDocExistsAsync(currentFilter) {
    var collection = getContext().getCollection();
    // 修复WHERE子句的语法错误,添加AND连接条件
    var queryText = "SELECT * FROM root r WHERE r.currentFilter = @currentFilter AND r.isDeleted = false";
    // 使用参数化查询提升安全性与性能
    var queryOptions = {
        parameters: [{ name: "@currentFilter", value: currentFilter }]
    };
    var response = await collection.queryDocuments(collection.getSelfLink(), queryText, queryOptions).toArrayAsync();
    // 存在则返回文档对象,不存在返回null
    return response.length > 0 ? response[0] : null;
}

2. 重构存储过程核心逻辑

将currentFilter设为文档id,利用Cosmos DB同一分区内id唯一的特性,同时添加冲突错误(409)的兜底处理,彻底避免重复创建:

async function CreateDocIfItDoesNotExistOrUpdate() {
    var collection = getContext().getCollection();
    var request = getContext().getRequest();
    var payload = request.body;
    var currentFilter = payload.currentFilter;
    var updateData = payload.updateData;

    // 1. 查询目标文档
    var existingDoc = await checkIfDocExistsAsync(currentFilter);

    if (existingDoc) {
        // 2. 文档存在:执行更新操作
        Object.assign(existingDoc, updateData); // 合并更新字段
        await collection.replaceDocument(existingDoc._self, existingDoc);
        getContext().getResponse().setBody({ status: "UPDATED", docId: existingDoc.id });
    } else {
        // 3. 文档不存在:创建新文档,用currentFilter作为id保证唯一性
        var newDoc = {
            id: currentFilter,
            currentFilter: currentFilter,
            isDeleted: false,
            ...updateData
        };
        try {
            await collection.createDocument(collection.getSelfLink(), newDoc);
            getContext().getResponse().setBody({ status: "CREATED", docId: newDoc.id });
        } catch (e) {
            // 兜底处理:竞态导致id冲突(409),重新查询后执行更新
            if (e.code === 409) {
                var conflictDoc = await checkIfDocExistsAsync(currentFilter);
                if (conflictDoc) {
                    Object.assign(conflictDoc, updateData);
                    await collection.replaceDocument(conflictDoc._self, conflictDoc);
                    getContext().getResponse().setBody({ status: "UPDATED_AFTER_CONFLICT", docId: conflictDoc.id });
                }
            } else {
                throw e;
            }
        }
    }
}

关键注意事项

  • 强制指定分区键:调用存储过程时必须传入与currentFilter对应的分区键值,确保同一业务标识的请求都路由到同一个分区,触发Cosmos DB的分区内串行执行机制。
  • 维持强一致性级别:已设置的强一致性确保查询能读取到最新的写入结果,避免“读不到刚写入的文档”的问题。
  • 利用唯一性约束:将业务唯一标识作为文档id是最可靠的防重复手段,Cosmos DB会直接拦截重复id的创建请求。

内容的提问来源于stack exchange,提问作者Ashokan Sivapragasam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 02:10:35