Python(boto3)中的 DynamoDB TransactWriteItems
事务是 boto3 两套 API 分歧最大的地方之一:transact_write_items 只存在于低层 client 上,所以 Table 带给你的那种原生 Python 类型的便利在这里没有。而当事务失败时,你需要的东西藏在异常的一个角落里,大多数 boto3 代码从来不去看那里。(事务能给你买到什么在每个 SDK 里都是一样的。)
代码
import boto3
client = boto3.client("dynamodb")
# Move one award between two songs — atomically. If the first song has no
# award to give, NEITHER update happens.
try:
client.transact_write_items(
TransactItems=[
{
"Update": {
"TableName": "Music",
"Key": {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
"UpdateExpression": "SET #upd0 = #upd0 - :one",
"ConditionExpression": "#upd0 >= :one",
"ExpressionAttributeNames": {"#upd0": "Awards"},
"ExpressionAttributeValues": {":one": {"N": "1"}},
}
},
{
"Update": {
"TableName": "Music",
"Key": {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "A Mis Abuelos"}},
"UpdateExpression": "SET #upd0 = if_not_exists(#upd0, :zero) + :one",
"ExpressionAttributeNames": {"#upd0": "Awards"},
"ExpressionAttributeValues": {":one": {"N": "1"}, ":zero": {"N": "0"}},
}
},
]
)
print("Transaction committed")
except client.exceptions.TransactionCanceledException as e:
# One reason per action, in TransactItems order. Code "None" means that
# action was fine — some OTHER action sank the transaction.
codes = [reason["Code"] for reason in e.response["CancellationReasons"]]
print(f"Transaction canceled: {codes}") # e.g. ['ConditionalCheckFailed', 'None']说明
TransactItems——一个由Put、Update、Delete和ConditionCheck字典组成的列表,每个值都必须是 DynamoDB JSON,没有例外。这是唯一一次带类型形式不可选的 boto3 调用,也正是本页末尾那一节存在的原因。上限在 CLI 那一页。CancellationReasons不在Error里面。botocore 会把建模出来的错误字段提到响应字典的顶层,所以捕获到的异常带着的e.response里,CancellationReasons、Error、Message和ResponseMetadata是并列的。到e.response["Error"]底下去找它什么也找不到,而e.response["Error"]里只有概要性的码和消息。None条目上没有"Message"——一个成功动作的原因是只有一个键的字典{"Code": "None"},所以那句自然而然的[r["Message"] for r in reasons]恰恰会在那些成功了的动作上抛出KeyError: 'Message'。用r.get("Message")。- 一个生成出来的异常类——botocore 在运行时从服务模型构建出
client.exceptions.TransactionCanceledException,所以它挂在 client 实例上,也因此你没法from botocore.exceptions import ...它。在一个拿不到 client 的辅助函数里,捕获botocore.exceptions.ClientError并按e.response["Error"]["Code"]分支;生成的那个类是它的子类。 - 结构性错误不会以取消的形式到达,所以代码片段里的
except子句永远看不到它们。两个动作瞄准同一个项目会抛出一个裸的ClientError,其码为ValidationException,而它的e.response里没有CancellationReasons键,因为事务在任何动作运行之前就被拒绝了。如果你想让这些也带着同样的上下文被记录下来,就在外层捕获ClientError。 - 动作上的
ReturnValuesOnConditionCheckFailure: "ALL_OLD"会把落败的那个项目以 DynamoDB JSON 放进该动作的原因里的Item键下,省掉你在已经输掉这场竞争之后再补一次get_item。 - boto3 会替你填
ClientRequestToken。在线上抓包看到,两次一模一样的transact_write_items调用发出的是两个不同的 UUID,所以这个 token 覆盖的是单次调用,而不是你自己的捕获-重试循环。如果重试可能比进程活得更久,就自己传一个稳定的。 - 在
TransactionConflict上重试,在ConditionalCheckFailed上永远不要重试——前者说的是别人短暂地占住了那个项目;后者说的是你的前置条件为假,而且下次还会为假。大多数处理逻辑只需要区分这两个码,完整集合在 TransactionCanceledException 页面上解码。 - 成本——一次事务写入的计费大约是同样的写入在事务之外的两倍,在 CLI 那一页测量过。如果你只需要单个项目上的原子性,条件写入以一半的价格就能买到。
这件事没有资源 API 的版本
boto3.resource("dynamodb").Table(...) 没有 transact_write_items 这个属性;只有 resource.meta.client 有。所以一个已经定型于 Table 和原生 Python 类型的代码库,做事务时只能退回带类型的 DynamoDB JSON,或者用 boto3.dynamodb.types.TypeSerializer 手工序列化:
from boto3.dynamodb.types import TypeSerializer
serialize = TypeSerializer().serialize
values = {k: serialize(v) for k, v in {":one": 1, ":zero": 0}.items()}TypeSerializer 应用的规则和资源 API 相同,也就是说它拒绝 float,任何带小数的东西都要 decimal.Decimal。当你只是想把一个字面量粘进脚本时,DynamoDB JSON 转换器会在浏览器里做同样的转换。要在不手写这两种形式中任何一种的情况下编辑事务碰到的项目,请下载 DynoTable。
相关示例
- Node.js 中的 DynamoDB TransactWriteItems——用 AWS SDK v3 做同样的事务。
- 用 AWS CLI 执行 DynamoDB TransactWriteItems——在 shell 里做同样的事务。
- Python 中的 DynamoDB 条件写入——不用付 2× 代价的单项目原子性。
- DynamoDB 事务——隔离性、幂等性,以及事务什么时候值得用。
- DynamoDB TransactionCanceledException——每一个取消原因码,逐个解码。
- "Too many actions in a TransactWriteItems call"——100 个动作与 4 MB 的事务限制。
- "Transaction request cannot include multiple operations on one item"——每个事务里每个项目只能有一个动作。
参考资料
- TransactWriteItems — Amazon DynamoDB API Reference
- DynamoDB.Client.transact_write_items — Boto3 documentation
- Amazon DynamoDB transactions: how it works — Amazon DynamoDB Developer Guide
- DynamoDB read and write operations (capacity unit consumption) — Amazon DynamoDB Developer Guide
最后核实于 2026-07-28,依据上方链接的 AWS 官方文档。