入门阅读约 3 分钟

DynamoDB 中的分页

DynamoDB 从不在一次调用中返回“全部”结果。一次 QueryScan 最多返回 1 MB 数据,然后交给你一个 LastEvaluatedKey 以便继续。把分页做对,意味着围绕这个键循环 —— 而不是围绕一个计数器。

循环

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

LastEvaluatedKeyundefined 时,你已经到达末尾。把它作为 ExclusiveStartKey 传回,即可获取下一片。

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

presentabsentQuery / ScanProcess ItemsLastEvaluatedKey?Set ExclusiveStartKeyDone

每一趟要么从返回的键继续,要么停止 —— 这里没有计数器。

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,那通常是该改为添加一个索引的信号。

试用 DynoTable,以可视化方式翻阅查询结果,游标会替你跟踪。

更新于