Python(boto3)中的 DynamoDB 条件写入
boto3 是唯一一个给条件写入配了具名异常类可供 catch 的 SDK,也是唯一一个把返回的项藏在你猜不到的地方的 SDK。表达式本身在哪儿都一样;DynamoDB 条件表达式讲了这些函数和乐观锁模式。
代码
import boto3
client = boto3.client("dynamodb")
# Update the item only if nobody changed it since we read version 7.
try:
client.update_item(
TableName="Music",
Key={"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
UpdateExpression="SET #upd0 = :updValue0, #version = :newVersion",
ConditionExpression="attribute_exists(#cond0) AND #version = :expectedVersion",
ExpressionAttributeNames={"#upd0": "Genre", "#version": "Version", "#cond0": "Artist"},
ExpressionAttributeValues={
":updValue0": {"S": "Latin Jazz"},
":expectedVersion": {"N": "7"},
":newVersion": {"N": "8"},
},
ReturnValuesOnConditionCheckFailure="ALL_OLD",
)
print("Updated to version 8")
except client.exceptions.ConditionalCheckFailedException as e:
# With ReturnValuesOnConditionCheckFailure="ALL_OLD", the current item
# rides back on the exception — no extra read to see what beat you.
print("Lost the race — item is now:", e.response.get("Item"))说明
ConditionalCheckFailedException是一个建模过的类,所以except client.exceptions.…能用。大多数 DynamoDB 错误不是这样:ValidationException根本没有对应的类,只能靠e.response["Error"]["Code"]去匹配。这个建模过的类仍然继承自ClientError,所以如果你把处理器的顺序排得马虎,上游一个宽泛的except ClientError就会把它吞掉。- 返回的项是
e.response的顶层键,不在e.response["Error"]里面。这就是代码里写e.response.get("Item")的原因。很容易顺着["Error"]去Code和Message旁边找,一无所获,然后断定这个参数没生效。 - 项是以 DynamoDB JSON 的形式回来的,哪怕你已经习惯了原生值,因为这里用的是低层客户端。想要普通的 Python 值,用
boto3.dynamodb.types.TypeDeserializer转换即可。 - 资源 API 用对象表达同一个防护,即
ConditionExpression=Attr("Version").eq(7) & Attr("Artist").exists(),用的是原生值,没有占位符映射。它抛出的是同一个异常,所以下面的处理方式不变。 - 失败的检查照样计一次写入。开发者指南明确说条件为 false 时会消耗写容量,按新旧两个项中较大的那个来计,所以在一个竞争激烈的键上无限重试是在真金白银地烧钱、却毫无进展。
boto3 把返回的项放在哪里
在存储的 Version 为 9 时运行上面这段代码,然后打印异常响应的键。DynamoDB Local 3.3.0,boto3 1.43.58:
sorted(e.response.keys()) -> ['Error', 'Item', 'ResponseMetadata']
e.response["Item"] -> {'Artist': {'S': 'Arturo Sandoval'},
'Year': {'N': '1994'},
'Version': {'N': '9'},
'SongTitle': {'S': 'Cubano Chant'},
'AlbumTitle': {'S': 'Danzon'}}去掉 ReturnValuesOnConditionCheckFailure,同样的失败给出的是 ['Error', 'ResponseMetadata']。Item 这个键不存在,而 e.response.get("Item") 返回 None 而不是抛异常。这就是那种能通过代码评审、然后在生产环境里开始记录 None 的 bug。
为什么表达式里每个名字都做了别名
代码里写的是 #version 和 #cond0,而不是 Version 和 Artist,对这两个普通单词来说看着像小题大做。就它们俩而言,确实是。Version 不是 DynamoDB 的保留字,直接裸用也能通过名字校验。
Year 才是保留字,而同一张表里正好有一个。直接拿它做防护,你会得到:
ValidationException: Invalid ConditionExpression: Attribute name is a reserved keyword;
reserved keyword: Year那份列表上有 573 个词,包括 Name、Status、Size、Count、Data、Owner、Timestamp 和 Items。给所有名字都加别名,是生成式代码避免去分辨谁是谁的办法。把你的属性名粘进保留字检查器,它会把需要别名的那些的 ExpressionAttributeNames 映射返回给你。
要在你自己的表上写这些防护、并把别名交给工具处理,下载 DynoTable。
相关示例
- Node.js 中的 DynamoDB 条件写入——用 AWS SDK v3 实现同一个乐观锁。
- 用 AWS CLI 做 DynamoDB 条件写入——从 shell 里实现同一个乐观锁。
- Python 中的 DynamoDB PutItem——只创建不覆盖的
attribute_not_exists写入。 - DynamoDB 条件表达式——每一个函数,以及配套的模式。
- 在多个属性上强制唯一性——条件与事务的组合用法。
- DynamoDB ConditionalCheckFailedException——当条件检查失败在预期之内时,怎么低成本地处理它。
参考资料
- UpdateItem — Amazon DynamoDB API Reference
- DynamoDB.Client.update_item — Boto3 documentation
- Condition expressions — Amazon DynamoDB Developer Guide
- DynamoDB read and write operations (capacity unit consumption) — Amazon DynamoDB Developer Guide
- Reserved words in DynamoDB — Amazon DynamoDB Developer Guide
最后核实于 2026-07-28,依据上方链接的 AWS 官方文档。