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

BatchGetItem 在一次请求里按主键取回多达 100 个项目。在 AWS SDK v3 里,这个调用必须写成循环,因为 UnprocessedKeys 是随一次_成功_的响应回来的,而不是随错误回来的。什么会把它填满,以及为什么 16 MB 和每分区 1 MB 才是要紧的数字,DynamoDB 批量操作里讲过。本页讲的是 v3 的这个调用,以及它交还给你的东西。

代码

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

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

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

let requestItems = {
  Music: {
    Keys: [
      {Artist: {S: 'Arturo Sandoval'}, SongTitle: {S: 'Cubano Chant'}},
      {Artist: {S: 'Arturo Sandoval'}, SongTitle: {S: 'A Mis Abuelos'}},
      {Artist: {S: 'Ella Fitzgerald'}, SongTitle: {S: 'Misty'}}
    ]
  }
};

const items = [];
let attempt = 0;

do {
  const response = await client.send(new BatchGetItemCommand({RequestItems: requestItems}));
  items.push(...(response.Responses?.Music ?? []));

  // A partial result is NOT an error: throttling, a >16 MB response, or an
  // internal failure returns the leftovers in UnprocessedKeys. Retry them
  // with exponential backoff.
  requestItems = response.UnprocessedKeys;
  if (requestItems && Object.keys(requestItems).length > 0) {
    attempt += 1;
    await sleep(Math.min(100 * 2 ** attempt, 5000));
  }
} while (requestItems && Object.keys(requestItems).length > 0);

console.log(`Fetched ${items.length} items`);

说明

  • 那些可选链不是防御性的噪音ResponsesUnprocessedKeys 在 v3 的类型里都是可选的,所以 response.Responses?.Music ?? [] 和那个 Object.keys() 守卫正是编译器要求的东西。在纯 JavaScript 里,它们是拦住第一个空响应抛错的东西。
  • err.name 分支。v3 把服务端错误码放在那里,而这个命令真正会抛出的两种失败都不可重试,所以它们绝不能掉进退避循环里。超过 100 个键会得到 ValidationException / Too many items requested for the BatchGetItem call;同一个键给两次会得到 Provided list of item keys contains duplicates。两者都在 Python 页面上原样复现过。
  • UnprocessedKeys 回来时就已经是 RequestItems 的形状,这也是循环能直接把它赋回去的唯一原因。它不是分页游标,也不意味着调用失败了。
  • 退避是 AWS 的指示,不是锦上添花。API 参考告诉你要用"an exponential backoff algorithm",因为立刻重试会落到同一个被限流的分区上。
  • ConsistentReadProjectionExpression 是按表设置的,写在每个 RequestItems 条目内部,而不是顶层。当这个映射只有一个键、看起来像个扁平请求时,这一点很容易漏掉。

响应实际返回的样子

针对三首歌都在的 DynamoDB Local 3.3.0 运行上面的代码,加上 ReturnConsumedCapacity: 'TOTAL',并打印歌名而不是数量:

order:            [ 'A Mis Abuelos', 'Misty', 'Cubano Chant' ]
UnprocessedKeys:  {}
ConsumedCapacity: [ { TableName: 'Music', CapacityUnits: 1.5 } ]

请求列的顺序是 Cubano Chant、A Mis Abuelos、Misty。响应一个位置都没对上,这正是循环要往一个扁平数组里 push、而不是按下标取值的原因。用键属性把项目和请求对回去,并把这些键包含进任何 ProjectionExpression 里,好让你还有东西可以对。

在同一个 Music 条目上设置 ConsistentRead: true,同样这三个键就要花 3 个单元而不是 1.5 个。三个各自小于 4 KB 的项目会被当成三次独立的 GetItem 读取来计费,最终一致性读每次半个单元,强一致性读每次一个单元。定价计算器会在你敲定读取模式之前,把这种按项目的算术换算成一个月度数字。

现在删掉 Misty 再跑一次:两个项目、一个空的 UnprocessedKeys,以及 1.0 个单元。那个缺失的键一分钱没算。这是 DynamoDB Local 的产物,不是契约;BatchGetItem 参考(2026-07-28 取回)写明,对不存在的项目的请求仍会按该读取类型消耗最低读取容量。别拿本地运行来给一批缓存未命中做容量规划。

想把一组键读回来、看看到底返回了什么,又不想先写这个循环,就下载 DynoTable

相关示例

参考资料

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

无需控制台即可使用 DynamoDB

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

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