DynamoDB 如何在 Map 属性存在时更新,否则静默忽略
支付老李
结论
可以用一次原子 UpdateItem 完成操作,即使 map entry 不存在也能正常返回。
这里不要使用 ConditionExpression。DynamoDB 的条件不成立时一定会返回 ConditionalCheckFailedException,无法配置成静默成功。可以使用 if_not_exists:entry 存在时读取它的数量,不存在时按 0 计算,随后执行 REMOVE。
推荐写法
{
"TableName": "Products",
"Key": {
"orgzviceid": {
"N": "3000161710"
},
"productid": {
"N": "11"
}
},
"UpdateExpression": "SET #qty = #qty + if_not_exists(#checkout.#checkoutID, :zero) REMOVE #checkout.#checkoutID",
"ExpressionAttributeNames": {
"#checkout": "checkout",
"#checkoutID": "235",
"#qty": "prod_stk_qty_i_i"
},
"ExpressionAttributeValues": {
":zero": {
"N": "0"
}
},
"ReturnValues": "ALL_NEW"
}
这条表达式会按以下方式执行:
entry 存在:
prod_stk_qty_i_i = prod_stk_qty_i_i + checkout.235
然后删除 checkout.235
entry 不存在:
prod_stk_qty_i_i = prod_stk_qty_i_i + 0
删除不存在的 checkout.235,不产生实际变化
重复执行时:
- 第一次调用会增加库存,并删除 checkout entry。
- 后续调用发现 entry 已不存在,只会加
0。 - 请求不会因为 entry 不存在而触发条件检查失败。
整个操作只需一次 UpdateItem,具有原子性,不必先调用 GetItem。
为什么原来的条件表达式会报错
原来的条件是:
ConditionExpression: attribute_exists(#checkout.#checkoutID)
它不会让 DynamoDB 在条件不成立时跳过更新并返回成功。它的含义是:
只有条件成立,更新才可以执行;否则整个请求以条件检查失败结束。
因此,当 checkout.235 不存在时,DynamoDB 会返回:
ConditionalCheckFailedException
这是条件写入的正常行为。如果继续使用 ConditionExpression,应用代码就需要捕获该异常,并把它视为“已经处理过”。
如果库存属性也可能不存在
如果不能保证 prod_stk_qty_i_i 一定存在,也可以为它设置默认值:
{
"UpdateExpression": "SET #qty = if_not_exists(#qty, :zero) + if_not_exists(#checkout.#checkoutID, :zero) REMOVE #checkout.#checkoutID",
"ExpressionAttributeNames": {
"#checkout": "checkout",
"#checkoutID": "235",
"#qty": "prod_stk_qty_i_i"
},
"ExpressionAttributeValues": {
":zero": {
"N": "0"
}
}
}
需要注意,目标主键不存在时,UpdateItem 可能创建新项目。如果不允许创建不存在的产品,可以保留一个只检查产品本身的条件:
"ConditionExpression": "attribute_exists(#productid)"
同时增加:
"#productid": "productid"
这样,即使缺少 checkout entry,请求也不会失败;但如果产品项目本身不存在,仍会返回 ConditionalCheckFailedException。
注意事项
checkout中保存的值必须是 DynamoDB Number,否则加法会失败。prod_stk_qty_i_i也必须是 Number。- 数字形式的 map key,例如
"235",应通过ExpressionAttributeNames引用。 - 不要用“先
GetItem、再UpdateItem”的方式实现幂等性,两次请求之间会有并发竞态。 - 如果业务必须区分“本次确实释放了库存”和“此前已经释放”,可以继续使用
ConditionExpression,并由调用端将ConditionalCheckFailedException视为幂等成功。DynamoDB 本身无法把条件失败转换成静默成功响应。
备注:内容仅供参考。
