DynamoDB 中的分页
DynamoDB 从不在一次调用中返回“全部”结果。一次 Query 或 Scan 最多返回 1 MB 数据,然后交给你一个 LastEvaluatedKey 以便继续。把分页做对,意味着围绕这个键循环 —— 而不是围绕一个计数器。
循环
let key;
do {
const out = await client.send(new QueryCommand({...params, ExclusiveStartKey: key}));
process(out.Items);
key = out.LastEvaluatedKey;
} while (key);当 LastEvaluatedKey 为 undefined 时,你已经到达末尾。把它作为 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,那通常是该改为添加一个索引的信号。
试用 DynoTable,以可视化方式翻阅查询结果,游标会替你跟踪。