Python(boto3)中的 DynamoDB UpdateItem
boto3 为这次调用给了你两个 client,而它们对「数字是什么」意见不一致。下面的低层 client 收发 DynamoDB JSON,其中每个数字都是带引号的字符串。resource("dynamodb").Table(...) 接受原生 Python 对象,彻底拒绝 float,并把数字以 decimal.Decimal 交还给你。挑哪一个,才是本页真正要做的决定。
代码
import boto3
client = boto3.client("dynamodb")
response = client.update_item(
TableName="Music",
Key={"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
UpdateExpression="SET #upd0 = :updValue0, #upd1 = :updValue1 ADD #upd2 :updValue2",
ExpressionAttributeNames={"#upd0": "Genre", "#upd1": "Year", "#upd2": "Awards"},
ExpressionAttributeValues={":updValue0": {"S": "Latin Jazz"}, ":updValue1": {"N": "1994"}, ":updValue2": {"N": "1"}},
ReturnValues="ALL_NEW",
)
print(response["Attributes"]) # the item after the update说明
- 子句语法不归 boto3 管。
UpdateExpression是它原样转发的一个不透明字符串;只有 DynamoDB 会解析它,所以写错的代价是一次往返。这里的ADD是那种能消掉「读-改-写」竞争的原子自增,在ConditionExpression里加attribute_exists(Artist)会把 upsert 变成只更新,其余内容在更新表达式里。 - 响应正好有两个顶层键:
Attributes和ResponseMetadata。没有状态字段要检查,也没有影响行数。只要调用返回了,它就成功了;ResponseMetadata带着你想写进日志的RequestId和HTTPStatusCode。 ReturnValues="UPDATED_NEW"是省钱的选项。它只返回表达式碰过的属性,在一个大项目上,这就是「读一个计数器」和「把整条记录搬回来」的区别。- 错误以
botocore.exceptions.ClientError的形式到达,你按e.response["Error"]["Code"]分支。少一个别名会产生ValidationException,消息是Invalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: Year。带类型的子类确实存在,但只作为 botocore 在 client 实例上生成的属性(client.exceptions.ConditionalCheckFailedException),永远不是可导入的符号,所以一个作用域里没有 client 的辅助函数只能用错误码字符串。
Decimal 还是 DynamoDB JSON,二选一
资源 API 在请求被构建之前就拒绝 float,而且消息把它想要什么说得清清楚楚:
TypeError: Float types are not supported. Use Decimal types instead.那是 boto3 自己的类型检查,不是 DynamoDB 的。通过资源 API 存入 Decimal("4.5"),再用两个 client 分别把同一个属性读回来,你会得到:
resource Rating: Decimal('4.5') Awards: Decimal('2')
client Rating: {'N': '4.5'} Awards: {'N': '2'}两者都没错;它们是不同的契约。Decimal 保住了 DynamoDB 实际存储的精度,并逼你去认真对待算术,代价是 Decimal("1") * 2 会出现在期待 int 的代码里。低层 client 交给你字符串,解析留给你自己,上面那个代码片段就是这么做的。
由此得出的规则是:不要在同一条代码路径里混用它们。通过 Table.put_item 写入、又通过 client.get_item 读出的项目,形状是不同的,而这个 bug 会出现在你测得较少的那个分支里。
关于 TTL 属性的一点说明
Python 代码库里最常见的数值 SET 是 TTL:SET expires_at = :t,配上一个 Unix 纪元时间。DynamoDB 把那个属性读作秒。写成 int(time.time() * 1000) 的话,值是 1785269450912,当成秒解释就落在公元 58542 年,于是项目永远不会被删除,也没有任何东西报警。DynamoDB TTL 转换器会用两种单位把一个纪元时间读回来,并告诉你写进去的是哪一种。之后要从真实的表里把存进去的值读回来,请下载 DynoTable。
相关指南
- DynamoDB 更新表达式——
SET、ADD、REMOVE、DELETE以及惯用写法。 - 理解 ReturnValues——每个
ReturnValues选项给你什么。 - "Attribute name is a reserved keyword"——为什么这里的别名映射不是可选的。
- "Invalid UpdateExpression" 语法错误——常见的 SET/ADD 语法错误,逐个解码。
参考资料
- UpdateItem — Amazon DynamoDB API Reference
- update_item — Boto3 DynamoDB.Client Reference
- Update expressions — Amazon DynamoDB Developer Guide
最后核实于 2026-07-28,依据上方链接的 AWS 官方文档。