用 AWS CLI 做 DynamoDB 条件写入

条件写入从 shell 里发出去很简单,难的是 它的结果:一次失败真正有意思的部分是以错误、而不是以输出的形式回来的。DynamoDB 条件表达式讲的是表达式本身能表达什么;本页讲的是怎么从 CLI 运行一条,以及怎么把导致失败的那个项从错误里取出来。

代码

aws dynamodb update-item \
  --table-name 'Music' \
  --key '{"Artist":{"S":"Arturo Sandoval"},"SongTitle":{"S":"Cubano Chant"}}' \
  --update-expression 'SET #upd0 = :updValue0, #version = :newVersion' \
  --condition-expression 'attribute_exists(#cond0) AND #version = :expectedVersion' \
  --expression-attribute-names '{"#upd0":"Genre","#version":"Version","#cond0":"Artist"}' \
  --expression-attribute-values '{":updValue0":{"S":"Latin Jazz"},":expectedVersion":{"N":"7"},":newVersion":{"N":"8"}}'

成功时命令什么都不打印,退出码 0。如果有别的写入者抢先了,条件就会失败,CLI 会把服务端的消息报出来:

An error occurred (ConditionalCheckFailedException) when calling the UpdateItem operation:
The conditional request failed

说明

  • 成功是静默的。没有输出,退出码 0。没有东西可解析,也没有东西可断言,所以 shell 脚本只能把退出状态当成结果。想让更新后的项打印出来,就加上 --return-values ALL_NEW
  • 失败的退出码是 254,这是 CLI v2 表示客户端错误的代码,格式错误的请求也共用它。重试之前先按消息分支,否则表达式里的一个笔误就会变成一个无限退避循环。
  • --return-values-on-condition-check-failure ALL_OLD 在这里确实管用。合法取值是 ALL_OLDNONE,而且它不消耗读容量。要把项从错误里拿出来还得再加一个参数,下面会讲。
  • 条件和更新是两个独立的参数,却共用一个命名空间--expression-attribute-names--expression-attribute-values 会在 --update-expression--condition-expression 之间合并,所以生成的名字是 #upd0#cond0 这样一路排下去的,而不是每个子句从头开始。把同一个占位符用作两种含义,第二个会悄无声息地胜出。
  • 失败的写入照样计费。开发者指南写得很明确:条件求值为 false 时仍然会消耗写容量,按新旧两个项中较大的那个来计。条件不是一次廉价的存在性探测。

失败时的输出,以及怎么把项从里面取出来

把那段命令跑一次,它会静默地成功。当 Version 已经不是 7 时再跑第二次,aws-cli/2.36.9 会向 stderr 打印:

aws: [ERROR]: An error occurred (ConditionalCheckFailedException) when calling the UpdateItem operation: The conditional request failed

加上 --return-values-on-condition-check-failure ALL_OLD,默认输出会告诉你"还有更多内容",却并不给你看:

aws: [ERROR]: An error occurred (ConditionalCheckFailedException) when calling the UpdateItem operation: The conditional request failed

Additional error details:
Item: <complex value>
Use "--cli-error-format json" or another error format to see the full details.

<complex value> 就是那个项,被默认的文本渲染器扣下了。再加上 --cli-error-format json,整段内容就都打印出来:

{
    "Message": "The conditional request failed",
    "Code": "ConditionalCheckFailedException",
    "Item": {
        "Artist": {"S": "Arturo Sandoval"},
        "Year": {"N": "1994"},
        "Version": {"N": "8"},
        "SongTitle": {"S": "Cubano Chant"},
        "AlbumTitle": {"S": "Danzon"},
        "Genre": {"S": "Latin Jazz"}
    }
}

(属性映射各自折成了一行;其余部分都是原样打印的。)Version 是 8、Genre 已被设置,因为第一次运行成功了。这就是从 shell 脚本里闭合的乐观锁循环:把 stderr 通过 jq -r '.Item.Version.N' 过一遍,再把结果作为 :expectedVersion 喂回去,重试。不需要 get-item,读取和重试之间也没有留给第三个写入者插进来的窗口。

重试并不免费。每次被拒绝的尝试都会消耗一个写单元,所以一个竞争激烈的键在紧凑循环下会稳定地烧钱、却毫无进展。如果你想在给尝试次数封顶之前先知道一场重试风暴到底花多少钱,定价计算器能把写入速率换算成月度金额。

要在你自己的表上运行这些防护、又不必在 shell 里给占位符映射逐个加引号,下载 DynoTable

相关示例

参考资料

最后核实于 2026-07-28,依据上方链接的 AWS 官方文档。

无需控制台即可使用 DynamoDB

一款快速的 DynamoDB 桌面客户端,可运行 DynamoDB 无法执行的真正 SQL——JOINs、GROUP BY、聚合——并支持可视化编辑和运行在你自己的 Bedrock 密钥上的 AI agent。

30 天免费试用,无需信用卡 — 之后为无时间限制的免费版。