入门阅读约 3 分钟

DynamoDB 中的分页

DynamoDB 绝不会在一次调用里返回"全部"结果。一次 QueryScan 最多返回 1 MB 数据,然后交给你一个 LastEvaluatedKey 用来续读。把分页做对,意味着围绕这个键循环——而不是围绕一个计数器。

DynamoDB 中的分页是如何工作的?

一次 QueryScan 每次调用最多返回 1 MB,然后交回一个 LastEvaluatedKey。要翻页,你把那个键作为下一次调用的 ExclusiveStartKey 传入,循环下去,直到 DynamoDB 不再返回键。没有页码、没有总数,而 Limit 限制的是被评估的项——不是被返回的项。

let key;
do {
  const out = await client.send(new QueryCommand({...params, ExclusiveStartKey: key}));
  process(out.Items);
  key = out.LastEvaluatedKey;
} while (key);

LastEvaluatedKeyundefined 时,你就到头了。把它作为 ExclusiveStartKey 传回去,即可取下一片。

控制流是一个单一循环,只有在键缺失时才退出:

存在缺失Query / Scan处理 ItemsLastEvaluatedKey?设置 ExclusiveStartKey完成

每一趟要么从返回的键续读,要么停下——没有计数器。

Limit 不是页大小

Limit 限制的是 DynamoDB 评估多少项,而不是它在应用 FilterExpression 之后返回多少项。一个 Limit: 25 且带筛选的查询可能只返回 3 个项,却仍然交给你一个 LastEvaluatedKey——你必须一直翻页,直到键为空,哪怕某一页看起来很短。非空LastEvaluatedKey 也从不承诺还有更多匹配的项;只有缺失的键才证明你已到头。

让 SDK 替你分页

两个 SDK 都封装了上面的循环,让你可以直接迭代页:

// AWS SDK for JavaScript v3
import {paginateQuery} from '@aws-sdk/lib-dynamodb';
for await (const page of paginateQuery({client}, params)) {
  process(page.Items);
}
# boto3
paginator = client.get_paginator('query')
for page in paginator.paginate(**params):
    process(page['Items'])

没有页码

DynamoDB 没有总数、没有随机页访问——你无法跳到"第 7 页",也无法在不重放游标的情况下往回翻。要围绕无限滚动 /"加载更多"来设计 UI,而不是带编号的页。(一个 Select: 'COUNT' 查询为了计数,仍然会读取——并按每一个匹配的项计费。)

面向 API 的无状态游标

LastEvaluatedKey 只不过是最后一个项的键属性。把它 base64 编码,作为一个不透明的 nextToken 交给客户端;在下一次请求时把它解码回 ExclusiveStartKey。没有服务端游标状态。

那个 token 是 DynamoDB-JSON——用 DynamoDB-JSON 转换器肉眼看或手工造一个。如果你分页是为了绕开某个 Scan,那通常是个信号:改为添加一个索引才对。

要完全跳过手写这个循环,查询构建器会组装完整的 Query/Scan 请求,并输出可运行的 SDK v3、CLI 或 boto3 程序——分页循环也包含在内。

试用 DynoTable,可视化地翻阅查询结果,而游标由它替你跟踪。

更新于