DynamoDB 事务
一次 DynamoDB 事务把多个写入归为一次全有或全无的操作:要么每个动作都提交,要么一个都不 提交。这就是你在一次写入半途失败时,让两个相关的项不至于彼此偏离的方式。
在审计日志的场景中,你追加的每个事件都应同时让某个按租户的 eventCount 加一。如果事件落地
而计数器没有 —— 或反过来 —— 那么日志和计数就会永远不一致。一次事务让这种情况成为不可能。
DynamoDB 支持事务吗?
是的。DynamoDB 通过 TransactWriteItems 和 TransactGetItems 支持 ACID 事务,可将最多 100 个动作归集为一次跨一张或多张表的全有或全无操作。要么每个写入都提交,要么一个都不提交,相关项因此不会彼此偏离。事务写入消耗的容量是普通写入的两倍,且一旦条件失败或发生冲突,整个请求就会被取消。
TransactWriteItems把最多 100 个写入动作归为一组,跨一张或多张表,全有或全无。这些项 的总大小不能超过 4 MB。- 动作是
Put、Update、Delete和ConditionCheck。ConditionCheck对一个你并不写入 的项断言某事。 - 它的成本翻倍。 一次事务写入消耗的容量是普通写入的两倍 —— DynamoDB 先准备后提交。
- 冲突和失败的条件会取消整件事,并抛出
TransactionCanceledException;不会留下任何残缺的 部分。
问题所在:两个必须一致的写入
你希望每个新审计事件也让该租户的运行计数加一。作为两次独立调用来做,它们之间的任何失败都会 损坏你的数据:
PutItem写入新的EVENT#…项 —— 成功。UpdateItem执行ADD eventCount 1—— 超时。
现在日志比计数器所声称的多了一行。盲目重试第 2 步有重复计数的风险;不重试又会让它们不一致。 没有安全的恢复办法,因为这两个写入从未被关联起来。
从 SQL 过来,你会把两者都裹进 BEGIN … COMMIT。DynamoDB 给出的答案是一次单一的事务请求,
它把两个写入一起携带。
TransactWriteItems 如何工作
据
AWS 开发者指南所述,
TransactWriteItems “在一次全有或全无的操作中归集最多 100 个写入动作”,针对最多 100 个不同的项,
且“事务中各项的总大小不能超过 4 MB”。这些动作原子性地完成 —— 要么全部成功,要么一个都不成功。
你可以在一次事务中混用四种动作类型:
Put—— 创建或替换一个项。Update—— 编辑属性(包括用ADD来处理我们的计数器)。Delete—— 按键移除一个项。ConditionCheck—— 对一个你并不另行写入的项断言一个条件(例如“此租户仍处于活跃状态”)。
实践中还有两条规则会起作用。第一,事务消耗的容量是等价非事务写入的两倍 —— DynamoDB 会做 一个准备阶段和一个提交阶段。第二,你不能在一次事务中两次针对同一个项,而且事务不能针对 索引执行。
一个实战示例:原子地追加 + 计数
回到审计日志。为租户 acme 追加一个事件并让它的计数器加一,是一次带两个动作的事务:
| action | item | effect | |
|---|---|---|---|
| Put | TENANT#acme | EVENT#2026-06-24T09:14Z#a1 | write the new audit row |
| Update | TENANT#acme | COUNTER | ADD eventCount 1 |
如果任一动作的条件失败 —— 比如一个断言该租户未被停用的 ConditionCheck —— 整个请求就会带着
TransactionCanceledException 被取消,且两个写入都不发生。日志和计数器永远不会不一致。
每个动作上的 ConditionExpression 就是那个杠杆。要断言事件行尚不存在(这样一次重试就不会
重复它)且该租户处于活跃状态,你可以这样组合条件:在 Put 上用 attribute_not_exists(SK),
并把 status = :active 作为一个 ConditionCheck。
在 DynamoDB 表达式构建器中构建并复制那些带类型的条件
表达式,而不要手动拼凑 ExpressionAttributeNames 和 :val 占位符 —— 它的条件写入模式会精确
地生成 TransactWriteItems 所要的结构。
对于在不稳定连接上的安全重试,附上一个 client token:在 10 分钟内用同一个 token 重复
TransactWriteItems 会返回成功而不重新应用那些写入
(幂等性)。
在 DynoTable 中操作
DynoTable 在底层用事务来完成它自己的写入:当你暂存若干项编辑并提交它们时,它会把它们作为带
乐观锁条件表达式的 TransactWriteItems 发送,于是你这一批编辑要么全有要么全无 —— 你绝不会
半途应用一次多项更改。
这意味着你可以在同一个暂存批次里编辑事件行和计数器、审阅差异,然后原子地一并提交,而无需 编写任何 SDK 代码。

坑与后续步骤
- 为双倍容量做预算。 一次事务写入计费的 WCU 是普通写入的两倍 —— 对于偶尔的、对一致性至关 重要的成对操作没问题,但如果把每一个写入都裹进事务里就代价高昂。在原子性真正要紧的地方使用它。
- 显式处理
TransactionCanceledException。 它在条件失败或与同一些项上另一个进行中的事务 发生冲突时被返回。取消原因会告诉你是哪个动作失败了 —— 去检查它们,不要盲目重试。 - Stream 记录不感知事务。 一次事务带来的变更会逐步传播到 Streams,并可能与其他变更交错; 消费者不能假定原子性或顺序 —— 参见 DynamoDB Streams。
- 不适用于高吞吐计数器。 一个单独的热点计数器在繁重的并发事务负载下会被限流;对此,请看 原子计数器或对计数器做分片。
事务是用于“这些写入必须一致”的工具。一旦事件开始被一致地落地,下一个考量就是对它们做出反应 —— 那就是 DynamoDB Streams。
下载 DynoTable,暂存多项编辑并把它们作为一次事务针对你自己的表提交。


