Node.js 中的 DynamoDB TransactWriteItems(AWS SDK v3)

一次成功的 TransactWriteItemsCommand 几乎什么都不告诉你:没有项目、没有属性、一个空响应。你需要的一切都在异常上,所以在 SDK v3 里,下面那个 catch 块才是真正的 API 表面,而且值得确切地知道会有什么落进去。(至于事务到底什么时候才是对的选择,参见 DynamoDB 事务。)

代码

import {DynamoDBClient, TransactWriteItemsCommand} from '@aws-sdk/client-dynamodb';

const client = new DynamoDBClient({region: 'us-east-1'});

// Move one award between two songs — atomically. If the first song has no
// award to give, NEITHER update happens.
const command = new TransactWriteItemsCommand({
  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'}}
      }
    }
  ]
});

try {
  await client.send(command);
  console.log('Transaction committed');
} catch (err) {
  if (err.name === 'TransactionCanceledException') {
    // One reason per action, in TransactItems order. 'None' means that action
    // was fine — some OTHER action sank the transaction.
    const codes = (err.CancellationReasons ?? []).map((r) => r.Code);
    console.log('Transaction canceled:', codes); // e.g. ['ConditionalCheckFailed', 'None']
  } else {
    throw err;
  }
}

说明

  • TransactItems——一个由 PutUpdateDeleteConditionCheck 动作组成的有序数组。这个顺序不是执行顺序(事务是原子的),但它_确实_是失败原因回来时的顺序,而这也是你唯一需要在意它的理由。上限在下文讲。
  • v3 实际抛出的是什么。捕获到的对象自身的属性是 $fault$retryable$metadatanameCancellationReasonsmessage__type。没有 err.code;要 switch 的字符串是 err.name,而 err.$metadata 带着 httpStatusCode: 400 外加 attempts: 1——你正是靠它才能判断 SDK 没有悄悄替你重试这次取消。
  • CancellationReasons 是按位置的,而且是稀疏的。对上面这个事务,它回来时是 [{"Code":"ConditionalCheckFailed","Message":"The conditional request failed"},{"Code":"None"}]。那个 None 条目根本没有 Message 属性,所以 err.CancellationReasons.map((r) => r.Message.trim()) 会在你的错误处理器内部、恰恰在那些成功了的动作上抛错。
  • ReturnValuesOnConditionCheckFailure: 'ALL_OLD' 会给那个动作的原因加上一个 Item,排在 CodeMessage 前面,格式是原始的 DynamoDB JSON。落败项目的属性白送给你;不然的话,你就得在已经输掉这场竞争之后再补一次 GetItem
  • err.name 这个判断有个缺口,而且值得知道是哪一个。把两个动作指向同一个项目,DynamoDB 会回答 ValidationException,消息是 Transaction request cannot include multiple operations on one item,并且完全没有 CancellationReasons,因为什么都还没尝试。上面那个 else { throw err } 分支会把它重新抛出去。这是正确行为,不是 bug,但它意味着结构性的错误永远到不了你的取消日志里。
  • v3 本来就会发送 ClientRequestToken,哪怕你没写它。抓取序列化后的请求体能看到线上有一个全新的 UUID,而对同一个命令对象调用两次 send() 发出去的是两个不同的令牌。所以这个令牌保护的是一次在途调用,而不是你自己的重试循环:catch 之后重发,你拿到的是一个新令牌和零幂等性。如果一次重试可能跨越进程边界,就自己提供一个。改了任何参数还复用它,你得到的是 IdempotentParameterMismatch,而不是悄无声息地重复生效。
  • 只有另外一个码需要单独的代码路径TransactionConflict 表示有个并发事务占住了你的某个项目,所以带退避的重试是对的应对,而对 ConditionalCheckFailed 则永远不是。其余的在 TransactionCanceledException 页面上有逐一解读
  • 成本——事务里的每个项目在底层都会被写两次(准备,然后提交),所以按普通写入约 2 倍的写入容量来做预算。单项目的条件写入以一半的代价给你单个项目上的原子性。

你会先撞上哪个上限

100 个动作的上限和 4 MB 的上限是彼此独立的,而让人意外的是字节那个:一百次计数器自增算不了什么,而十来个大项目光靠自己就能把总量吃光。在你决定一次批多少个动作之前,先用 DynamoDB 项目大小计算器量一个有代表性的项目。想在还在写条件的时候就读一读某个动作将要碰到的项目,就下载 DynoTable

相关示例

参考资料

最后核实于 2026-07-28,依据上方链接的 AWS 官方文档。

无需控制台即可使用 DynamoDB

一款快速的 DynamoDB 桌面客户端,可运行 DynamoDB 无法执行的真正 SQL——JOINs、GROUP BY、聚合——并支持可视化编辑和运行在你自己的 Bedrock 密钥上的 AI agent。

30 天免费试用,无需信用卡 — 之后为无时间限制的免费版。