DynamoDB TransactionCanceledException

TL;DR — 你 TransactWriteItems / TransactGetItems 里有一个(或多个)项目失败了,于是 DynamoDB 回滚了_整个_事务。真正的原因在 CancellationReasons 数组里——去读它;每个项目的原因 Code 会准确告诉你是哪个项目、以及为什么。

含义

DynamoDB 的事务是全有或全无的。只要任何一个项目的条件不成立、容量超了,或者两个事务撞在一起,整件事就会被取消,什么都不会写入。顶层的消息是笼统的:

TransactionCanceledException: Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None, TransactionConflict]

方括号里的列表是按位置对应的——你事务里每个项目一项,顺序一致。DynamoDB 以 HTTP 状态码 400 返回这个异常,而且 AWS SDK 不会自动重试它——由你的代码按原因代码逐个决定重试是否说得通。

为什么会发生(原因代码)

  • ConditionalCheckFailed——那个项目的 ConditionExpression 求值为假(见 ConditionalCheckFailedException)。
  • TransactionConflict——另一个并发事务(或写入)正在操作同一个项目;带退避重试。
  • ProvisionedThroughputExceeded——该项目所在的表/索引容量用光了。
  • ThrottlingError——表或索引限流了这次写入(通常是按需模式,而 DynamoDB 还在给它扩容);带退避重试。
  • ValidationError——那个项目本身格式不对(参数值、文档路径、操作数类型、大小溢出……)。
  • ItemCollectionSizeLimitExceeded——某个 LSI 项目集合撞上了 10 GB。
  • None——那个项目本身没问题;失败发生在列表里的别处。

以上就是完整的、有文档记载的代码集合。注意,主键重复(同一个项目被两个动作指向)并不是一个取消原因代码——DynamoDB 会在一开始就把那种请求作为 ValidationException 拒掉。

如何修复

  1. 从错误上读 CancellationReasons,而不是只看消息。按下标把每一项映射回你的输入项目。
  2. 按代码分支处理:ConditionalCheckFailed → 业务逻辑;TransactionConflict/ThrottlingError/ProvisionedThroughputExceeded → 带指数退避重试;ValidationError → 修请求。
  3. 避免重复的键——单个事务不能对同一个项目动两次。

在手工编辑项目?DynoTable 的暂存区会把你的改动打包成一次事务性写入,并让你在提交前逐项复核——同样的全有或全无语义,却不必手工构造请求。

示例

import {DynamoDBClient, TransactionCanceledException} from '@aws-sdk/client-dynamodb';
import {DynamoDBDocumentClient, TransactWriteCommand} from '@aws-sdk/lib-dynamodb';

const doc = DynamoDBDocumentClient.from(new DynamoDBClient({}));

try {
  await doc.send(new TransactWriteCommand({TransactItems: [/* ... */]}));
} catch (err) {
  if (err instanceof TransactionCanceledException) {
    for (const [i, reason] of (err.CancellationReasons ?? []).entries()) {
      if (reason.Code && reason.Code !== 'None') {
        console.error(`item ${i} cancelled: ${reason.Code}${reason.Message}`);
      }
    }
  }
  throw err;
}

常见问题

我的 DynamoDB 事务为什么被取消了? TransactWriteItems/TransactGetItems 里有一个项目失败了——一次条件检查、一个吞吐/限流上限,或者与另一个并发事务的冲突——于是 DynamoDB 把整个事务回滚,什么都没写。逐项目的原因在 CancellationReasons 数组里。

我怎么找出事务里是哪个项目失败了? 读 TransactionCanceledException 上的 CancellationReasons 数组。它对每个输入项目有一项,顺序相同;Code 不是 "None" 的那一项就是导致取消的项目。

复现方法

一个事务里的两次写入,第二次由一个不可能成立的条件把守。整个事务回滚,而逐动作的判定会出现在 CancellationReasons 里——与 TransactItems 按位置对齐:

await client.send(
  new TransactWriteItemsCommand({
    TransactItems: [
      {Put: {TableName: 'orders', Item: {pk: {S: 'OK'}, sk: {S: 'META'}}}},
      {
        Put: {
          TableName: 'orders',
          Item: {pk: {S: 'ORDER#1'}, sk: {S: 'META'}},
          ConditionExpression: 'attribute_not_exists(pk)' // ORDER#1 already exists
        }
      }
    ]
  })
);

实际输出:

TransactionCanceledException: Transaction cancelled, please refer cancellation reasons for specific reasons [None, ConditionalCheckFailed]
HTTP 400

error.CancellationReasons:
[
  {
    "Code": "None"
  },
  {
    "Code": "ConditionalCheckFailed",
    "Message": "The conditional request failed"
  }
]

第一个动作报告的是 None——它并没有失败,它是因为邻居失败了才被回滚的。只有 Code 不是 None 的那一项才指认出真正的罪魁祸首,而它的下标就是出问题的那个动作在你自己 TransactItems 数组里的下标。

相关错误

参考资料

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

2026-07-26 针对 DynamoDB Local 2.x 与 AWS SDK for JavaScript v3.1095.0 复现——上方输出为原样照录。

无需控制台即可使用 DynamoDB

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

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