You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中如何实现DynamoDB类似SQL/MySQL的分页(支持总计数和任意页跳转)

C# 实现DynamoDB类MySQL分页功能方案

核心前提

DynamoDB原生仅支持基于游标(LastEvaluatedKey)的分页,没有内置offset跳页、首次查询返回总条数的能力,要实现和SQL/MySQL完全一致的分页效果,可根据你的数据量选择以下两种落地方案:

方案1:小数据量场景(单查询匹配结果≤10W条)

无需额外改造表结构,直接基于原生接口封装即可,适配绝大多数低频次后台查询场景:

实现逻辑

  1. 首次查询时先发起COUNT类型请求,直接获取匹配条件的总数据条数,不会拉取全量数据,性能开销极低
  2. 跳转到指定页码时,先遍历对应页码之前的所有数据,记录最后一条的游标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开销和延迟,可通过新增分页元数据表优化:

实现逻辑

  1. 新建分页索引表,表结构参考:
    • PK:查询维度标识(比如你要按公司ID查询,就存Company#{公司ID})
    • 其他字段:TotalCount(总条数)、PageIndex(页码)、StartKey(对应页的起始游标)
  2. 当原表有符合查询维度的数据新增/删除时,异步更新分页索引表的总条数和各页的起始游标
  3. 用户查询指定页码时,直接从分页索引表取对应页的StartKey和TotalCount,一次Query即可拿到当前页数据,性能和MySQL分页完全一致

注意事项

  • 如果业务允许仅支持上下页翻页,优先使用原生的LastEvaluatedKey游标分页,性能最高、成本最低
  • 方案1不适用于高频C端查询场景,仅适合后台管理等低频次查询场景
  • 不要使用全量扫描拉取所有主键再做内存分页的方案,数据量过大时会产生严重的内存和性能问题

内容的提问来源于stack exchange,提问作者Hitesh Anshani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 15:15:05