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

下面这个 do/while 不是防御性写法。一个带过滤的 Scan 页面可能带着空的 Items 数组回来,而表里还剩着一大截没扫,所以在第一个响应处就停下,正是一次扫描在明明有匹配项的表上报告零匹配的方式。Query vs. Scan讲了什么时候该彻底避开这个操作。

代码

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

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

const items = [];
let lastEvaluatedKey;

do {
  const response = await client.send(
    new ScanCommand({
      TableName: 'Music',
      FilterExpression: '#filter0 >= :filterValue0',
      ExpressionAttributeNames: {
        '#filter0': 'Year'
      },
      ExpressionAttributeValues: {
        ':filterValue0': {N: '2010'}
      },
      ExclusiveStartKey: lastEvaluatedKey
    })
  );

  items.push(...(response.Items ?? []));
  lastEvaluatedKey = response.LastEvaluatedKey;
} while (lastEvaluatedKey);

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

两个空页面、284.5 个读取单元、8 个项目

这份固定数据是 600 首歌,每首大约 3.9 KB,其中正好有 8 首 Year >= 2010,而且它们排在最后。上面那个循环实际收到的是:

往返次序Items.lengthScannedCount读取单元LastEvaluatedKey
10271128.5
20271128.5
385827.5

连续两页什么都没返回,而且各花掉 128.5 个读取单元。写成 if (!response.Items.length) return 的代码会报告一张空表。API 参考把规则说得很直白:"a scan result can result in no items meeting the criteria and the Count will result in zero",另外还有一句"a FilterExpression is applied after the items have already been read; the process of filtering does not consume any additional read capacity units"。

第二句话请照着你的账单来读。过滤是免费的,而它丢掉的一切不是:284.5 个读取单元换来 8 个项目,和你完全不加过滤时付的账单一模一样。

2026-07-28 针对 9000 端口上的 DynamoDB Local(amazon/dynamodb-local),使用 node v24.18.0 上的 @aws-sdk/client-dynamodb 3.1095.0 实测。计数与容量均为引擎自己的响应字段。

说明

  • 第一趟里 ExclusiveStartKey: lastEvaluatedKeyundefined。v3 的序列化器会丢掉值为 undefined 的成员,所以同一个对象字面量既能覆盖第一次请求,也能覆盖后续每一次。改成传 {} 会失败并报 ValidationException: The provided starting key is invalid
  • response.Items ?? [] 是在干实事。把它和上面那张表合起来看:空值合并让累加器在那些什么都没匹配到的页面上保持诚实,而 while 让循环越过它们继续活着。
  • #filter0 不是装饰Year 在 AWS 的保留字清单上,不做别名直接用会返回 ValidationException: Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year
  • Limit 数的是读到的项目,不是返回的项目。在这个过滤条件下,Limit: 10 得到的是 Count: 0ScannedCount: 10。它是应对容量尖峰的节流旋钮,不是"给我十条结果"的办法。
  • Segment / TotalSegments 会把一次全表扫描分摊到多个工作单元上。那切的是墙上时钟时间,不是成本——花掉的还是那 284.5 个单元,只是更快、更并发。

这在一张真实的表上要花多少钱

284.5 个读取单元换 8 个项目,这就是问题的形状,而且它随表线性增长,不随结果增长。在把一次带过滤的扫描放到热路径上之前,先在 DynamoDB 定价计算器里按你的项目大小和流量给整表读取标个价,然后拿它跟一个能把同样的访问模式变成 Query 的 GSI 比一比。

想在图形界面里探索表、用带过滤和分页的结果网格来看,就下载 DynoTable,而不是在脚本里盲扫。

相关指南

参考资料

可视化构建此请求

在免费的 DynamoDB 查询构建器中组装此操作 —— 键条件、筛选、索引、Limit、排序方向和分页循环 —— 再把它作为可运行的 SDK v3、CLI 或 boto3 程序复制回来。

打开 DynamoDB 查询构建器

无需控制台即可使用 DynamoDB

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

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