为什么DynamoDB使用LSI做反向分页时最后一页仍返回LastEvaluatedKey
DynamoDB的LastEvaluatedKey返回逻辑不区分正序/反向查询:只要当前查询的单次扫描操作没有遍历完该分区下所有符合条件的项,就会返回非空的LastEvaluatedKey,和当前返回结果是不是已经是最后一页没有绝对关联。反向分页时出现非空LastEvaluatedKey但无后续数据,通常是因为DynamoDB的存储块扫描逻辑、或者项的筛选过滤导致单次扫描提前结束,哪怕已经没有更多符合要求的项。
正确判断反向分页到达最后一页的方法
- 每次调用
Query接口时固定设置Limit参数(分页场景必须配置,否则返回所有符合条件的项,不存在分页判断需求) - 优先用返回的
Items数组长度做第一层判断:如果Items.length < 你设置的Limit值,直接判定为已经到达最后一页,无需再生成上一页/下一页的分页token,忽略非空的LastEvaluatedKey即可。 - 如果
Items.length == Limit值,再做二次校验:- 复制当前查询的所有参数,将
ExclusiveStartKey设置为本次返回的LastEvaluatedKey,将Limit修改为1,保持ScanIndexForward=false不变 - 发起这个轻量校验查询,如果返回的
Items为空,说明已经到达最后一页,不需要生成分页token;如果返回有数据,再按原有逻辑生成prevToken。
- 复制当前查询的所有参数,将
代码修改参考
params.ScanIndexForward = false; // 注意:params必须提前配置好固定的Limit参数,比如params.Limit = 10 response = await dynamoDb.query(paramsForQuery).promise(); const arrayLength = response.Items.length; const pageLimit = params.Limit; let hasPrevPage = false; // 先判断是否还有上一页 if (arrayLength < pageLimit) { hasPrevPage = false; } else { // 等于Limit时做轻量校验 if (response.LastEvaluatedKey) { const checkParams = {...paramsForQuery, ExclusiveStartKey: response.LastEvaluatedKey, Limit: 1 }; const checkRes = await dynamoDb.query(checkParams).promise(); hasPrevPage = checkRes.Items.length > 0; } else { hasPrevPage = false; } } LastEvalSortKey = { pk: userId, originalUrl: response.Items[0].originalUrl, }; if (sortBy === 'createdAt') LastEvalSortKey[sortBy] = response.Items[0].createdAt; if (sortBy === 'updatedAt') LastEvalSortKey[sortBy] = response.Items[0].updatedAt; if (sortBy === 'convertedUrl') LastEvalSortKey[sortBy] = response.Items[0].convertedUrl; return { items: response.Items, nextToken: arrayLength ? Base64.encodeURI(JSON.stringify(LastEvalSortKey)) : prevToken, // 只有hasPrevPage为true的时候才生成prevToken prevToken: hasPrevPage ? Base64.encodeURI(JSON.stringify(response.LastEvaluatedKey)) : undefined, };
注:如果查询时加了FilterExpression,该判断逻辑同样适用,FilterExpression是在扫描完存储块之后才过滤项,也会导致LastEvaluatedKey非空但过滤后没有后续项的情况。
内容的提问来源于stack exchange,提问作者Jatin Mehrotra
相关产品推荐
相关产品推荐

