用 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_OLD和NONE,而且它不消耗读容量。要把项从错误里拿出来还得再加一个参数,下面会讲。- 条件和更新是两个独立的参数,却共用一个命名空间。
--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。
相关示例
- Node.js 中的 DynamoDB 条件写入——用 AWS SDK v3 实现同一个乐观锁。
- Python 中的 DynamoDB 条件写入——用 boto3 实现同一个乐观锁。
- 用 AWS CLI 做 DynamoDB PutItem——只创建不覆盖的
attribute_not_exists写入。 - DynamoDB 条件表达式——每一个函数,以及配套的模式。
- 理解 ReturnValues——每个返回选项分别给你什么。
- DynamoDB ConditionalCheckFailedException——当条件检查失败在预期之内时,怎么低成本地处理它。
参考资料
- UpdateItem — Amazon DynamoDB API Reference
- update-item — AWS CLI Command Reference
- Condition expressions — Amazon DynamoDB Developer Guide
- DynamoDB read and write operations (capacity unit consumption) — Amazon DynamoDB Developer Guide
最后核实于 2026-07-28,依据上方链接的 AWS 官方文档。