C#中如何实现DynamoDB类似SQL/MySQL的分页(支持总计数和任意页跳转)
C# 实现DynamoDB类MySQL分页功能方案
核心前提
DynamoDB原生仅支持基于游标(LastEvaluatedKey)的分页,没有内置offset跳页、首次查询返回总条数的能力,要实现和SQL/MySQL完全一致的分页效果,可根据你的数据量选择以下两种落地方案:
方案1:小数据量场景(单查询匹配结果≤10W条)
无需额外改造表结构,直接基于原生接口封装即可,适配绝大多数低频次后台查询场景:
实现逻辑
- 首次查询时先发起COUNT类型请求,直接获取匹配条件的总数据条数,不会拉取全量数据,性能开销极低
- 跳转到指定页码时,先遍历对应页码之前的所有数据,记录最后一条的游标key,再用该key查询当前页数据
代码示例
// 定义分页返回结果类 public class DynamoDbPageResult<T> { public int TotalCount { get; set; } public int CurrentPage { get; set; } public int PageSize { get; set; } public List<T> Data { get; set; } } // 分页查询封装示例(以Query为例,Scan逻辑完全一致) public async Task<DynamoDbPageResult<Dictionary<string, AttributeValue>>> QueryByPageAsync(string tableName, string pkValue, int pageNum, int pageSize) { var result = new DynamoDbPageResult<Dictionary<string, AttributeValue>> { CurrentPage = pageNum, PageSize = pageSize }; // 第一步:先查总条数 var countRequest = new QueryRequest { TableName = tableName, KeyConditions = new Dictionary<string, Condition> { ["PK"] = new Condition { ComparisonOperator = ComparisonOperator.EQ, AttributeValueList = { new AttributeValue { S = pkValue } } } }, Select = Select.COUNT }; var countResp = await Client.QueryAsync(countRequest); result.TotalCount = countResp.Count; if (result.TotalCount == 0) { result.Data = new(); return result; } // 第二步:定位到目标页的起始游标 Dictionary<string, AttributeValue> lastKey = null; var skipCount = (pageNum - 1) * pageSize; // 已经跳过的条数 var skipped = 0; while (skipped < skipCount) { var skipRequest = new QueryRequest { TableName = tableName, KeyConditions = countRequest.KeyConditions, Limit = Math.Min(pageSize, skipCount - skipped), ExclusiveStartKey = lastKey, // 只返回主键,减少数据传输 ProjectionExpression = "PK, SK" }; var skipResp = await Client.QueryAsync(skipRequest); skipped += skipResp.Items.Count; lastKey = skipResp.LastEvaluatedKey; if (lastKey == null || lastKey.Count == 0) break; } // 第三步:查询当前页数据 var dataRequest = new QueryRequest { TableName = tableName, KeyConditions = countRequest.KeyConditions, Limit = pageSize, ExclusiveStartKey = lastKey }; var dataResp = await Client.QueryAsync(dataRequest); result.Data = dataResp.Items; return result; }
方案2:大数据量场景(单查询匹配结果>10W条)
方案1的跳页逻辑在数据量过大时会产生较高的RCU开销和延迟,可通过新增分页元数据表优化:
实现逻辑
- 新建分页索引表,表结构参考:
- PK:查询维度标识(比如你要按公司ID查询,就存
Company#{公司ID}) - 其他字段:TotalCount(总条数)、PageIndex(页码)、StartKey(对应页的起始游标)
- PK:查询维度标识(比如你要按公司ID查询,就存
- 当原表有符合查询维度的数据新增/删除时,异步更新分页索引表的总条数和各页的起始游标
- 用户查询指定页码时,直接从分页索引表取对应页的StartKey和TotalCount,一次Query即可拿到当前页数据,性能和MySQL分页完全一致
注意事项
- 如果业务允许仅支持上下页翻页,优先使用原生的
LastEvaluatedKey游标分页,性能最高、成本最低 - 方案1不适用于高频C端查询场景,仅适合后台管理等低频次查询场景
- 不要使用全量扫描拉取所有主键再做内存分页的方案,数据量过大时会产生严重的内存和性能问题
内容的提问来源于stack exchange,提问作者Hitesh Anshani
相关产品推荐
相关产品推荐

