DynamoDB仅按PK设置条件 实现单订阅仅一条PENDING变更记录方案
解决方案
核心问题说明
DynamoDB 所有单记录级别的操作(包括ConditionCheck、Put、Delete等)都要求必须指定完整主键(分区键+排序键),无法直接基于非主键属性或者部分主键做全局的存在性校验,这是原有代码无法运行的核心原因。
可行实现方案(成本最低,无前置查询)
推荐通过新增固定主键的哨兵记录实现原子校验,不需要修改原有业务记录结构,具体逻辑如下:
方案逻辑
- 为每个订阅新增一条唯一的哨兵记录,专门用于标记该订阅是否存在状态为
PENDING的变更:- 分区键与业务记录一致,为
subscriptionId - 排序键使用固定特殊值
#META#PENDING_FLAG(前缀加#避免和UUID格式的changeId冲突)
- 分区键与业务记录一致,为
- 提交新变更的事务拆分为3个原子操作:
- 条件检查:哨兵记录不存在,代表当前订阅无待处理变更
- 写入新的
PENDING状态业务变更记录 - 写入哨兵记录,标记当前订阅已有待处理变更
- 当原有
PENDING变更被更新为CANCELLED或DONE时,在同一个事务中删除对应的哨兵记录,释放锁允许后续新变更提交。
如果业务逻辑是新变更要覆盖旧的待处理变更而非拒绝,可在同一个事务中新增对旧待处理变更的状态更新操作即可,哨兵记录不需要调整。
代码示例
writeItems := []*dynamodb.TransactWriteItem{ // 原子校验:无待处理变更哨兵记录 { ConditionCheck: &dynamodb.ConditionCheck{ TableName: aws.String("subscription_changes"), ConditionExpression: aws.String("attribute_not_exists(SK)"), Key: map[string]*dynamodb.AttributeValue{ "subscriptionId": {S: aws.String(subscriptionId)}, "SK": {S: aws.String("#META#PENDING_FLAG")}, }, }, }, // 写入新的待处理变更业务记录 { Put: &dynamodb.Put{ TableName: aws.String("subscription_changes"), Item: attributesMap, }, }, // 写入哨兵记录,标记存在待处理变更 { Put: &dynamodb.Put{ TableName: aws.String("subscription_changes"), Item: map[string]*dynamodb.AttributeValue{ "subscriptionId": {S: aws.String(subscriptionId)}, "SK": {S: aws.String("#META#PENDING_FLAG")}, // 可选加TTL,避免服务异常崩溃未释放锁的极端场景,时间可设为变更最长执行周期的2~3倍 "ttl": {N: aws.String(strconv.FormatInt(time.Now().Add(24*time.Hour).Unix(), 10))}, }, }, }, }
方案优势
- 无前置查询,完全避免竞态条件,同一时间仅会有一个变更请求通过校验
- 对原有业务逻辑侵入极低,不需要修改已有的业务记录结构和查询逻辑
- 可选加TTL兜底,避免极端场景下死锁
内容的提问来源于stack exchange,提问作者user1016765
相关产品推荐
相关产品推荐

