PAYATHON 2026

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,不产生实际变化

重复执行时:

  1. 第一次调用会增加库存,并删除 checkout entry。
  2. 后续调用发现 entry 已不存在,只会加 0。
  3. 请求不会因为 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 本身无法把条件失败转换成静默成功响应。

备注:内容仅供参考。