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

Oracle 19c不修改存储过程实现分页获取数据的方法咨询

Oracle 19c存储过程百万行数据分页优化(不修改存储过程)

我熟悉Microsoft SQL Server,刚接触Oracle 19c数据库。现有C#代码能成功调用包存储过程PKG_GET_COMPONENT_DETAIL.pr_get_wip_comp_list_sorted,但这个存储过程返回超百万行数据,导致DataSet填充速度极慢。希望在不修改该存储过程的前提下,实现类似SQL Server的分页效果:

SELECT * FROM STORED_PROCEDURE OFFSET 50 ROWS FETCH NEXT 50 ROWS ONLY

比如把存储过程作为子查询来做分页查询。

现有C#调用代码

public async Task<List<DbModels.DocumentWipList>> GetWipDocumentsAsync(string sort = "limited_dodiss ASC")
{
    using (var connection = new OracleConnection(_configuration.GetConnectionString("OracleDev")))
    {
        using (var command = connection.CreateCommand())
        {
            connection.Open();
            command.CommandType = CommandType.StoredProcedure;
            command.CommandText = "PKG_GET_COMPONENT_DETAIL.pr_get_wip_comp_list_sorted";
            command.Parameters.Add("arg_sort", OracleDbType.Varchar2).Value = sort;
            command.Parameters.Add("io_cursor", OracleDbType.RefCursor).Direction = ParameterDirection.Output; 

            using (var da = new OracleDataAdapter())
            {
                da.SelectCommand = command;
                var dt = new DataTable();
                await Task.Run(() => da.Fill(dt)); 

                return MapDocumentWipList(dt);
            }
        }
    }
}

解决方案

方案1:数据库端分页(推荐)

Oracle无法直接从存储过程结果集做SELECT查询,但可以通过PL/SQL块先调用存储过程获取游标,将游标数据批量收集到集合中,再对集合做分页查询(仅返回分页后的数据,性能最优)。

实现代码

public async Task<List<DbModels.DocumentWipList>> GetWipDocumentsPagedAsync(int pageIndex, int pageSize, string sort = "limited_dodiss ASC")
{
    using (var connection = new OracleConnection(_configuration.GetConnectionString("OracleDev")))
    {
        await connection.OpenAsync();
        // 先确定存储过程返回的字段,定义匹配的PL/SQL记录类型(示例字段需替换为实际字段)
        var sql = @"
DECLARE
    v_cursor SYS_REFCURSOR;
    -- 定义和存储过程返回结构匹配的记录类型
    TYPE wip_comp_record IS RECORD (
        limited_dodiss VARCHAR2(100),
        doc_id NUMBER,
        create_date DATE
        -- 补充存储过程返回的其他字段
    );
    -- 定义对应的集合类型
    TYPE wip_comp_tab IS TABLE OF wip_comp_record;
    v_wip_comps wip_comp_tab;
BEGIN
    -- 调用原存储过程获取全量数据游标
    PKG_GET_COMPONENT_DETAIL.pr_get_wip_comp_list_sorted(:arg_sort, v_cursor);
    -- 将游标数据批量收集到集合中
    FETCH v_cursor BULK COLLECT INTO v_wip_comps;
    CLOSE v_cursor;
    -- 打开分页后的输出游标(Oracle 12c+支持OFFSET语法)
    OPEN :paged_cursor FOR
        SELECT * FROM TABLE(v_wip_comps)
        OFFSET :offset_rows ROWS FETCH NEXT :page_size ROWS ONLY;
END;";

        using (var command = new OracleCommand(sql, connection))
        {
            command.CommandType = CommandType.Text;
            // 原存储过程的排序参数
            command.Parameters.Add("arg_sort", OracleDbType.Varchar2).Value = sort;
            // 分页参数:offset_rows是跳过的行数,page_size是每页行数(pageIndex从0开始)
            int offsetRows = pageIndex * pageSize;
            command.Parameters.Add("offset_rows", OracleDbType.Int32).Value = offsetRows;
            command.Parameters.Add("page_size", OracleDbType.Int32).Value = pageSize;
            // 分页后的输出游标
            var pagedCursorParam = command.Parameters.Add("paged_cursor", OracleDbType.RefCursor);
            pagedCursorParam.Direction = ParameterDirection.Output;

            using (var da = new OracleDataAdapter(command))
            {
                var dt = new DataTable();
                await Task.Run(() => da.Fill(dt));
                return MapDocumentWipList(dt);
            }
        }
    }
}

方案2:应用端分页(应急用)

如果不想编写PL/SQL逻辑,可以用OracleDataReader逐行读取数据,跳过前N行后取指定数量的行。但这种方式依然会把全量数据从数据库传到应用端,仅适合临时调试或小数据量场景:

public async Task<List<DbModels.DocumentWipList>> GetWipDocumentsPagedAsync(int pageIndex, int pageSize, string sort = "limited_dodiss ASC")
{
    using (var connection = new OracleConnection(_configuration.GetConnectionString("OracleDev")))
    {
        await connection.OpenAsync();
        using (var command = connection.CreateCommand())
        {
            command.CommandType = CommandType.StoredProcedure;
            command.CommandText = "PKG_GET_COMPONENT_DETAIL.pr_get_wip_comp_list_sorted";
            command.Parameters.Add("arg_sort", OracleDbType.Varchar2).Value = sort;
            var cursorParam = command.Parameters.Add("io_cursor", OracleDbType.RefCursor);
            cursorParam.Direction = ParameterDirection.Output;

            var result = new List<DbModels.DocumentWipList>();
            int skipCount = pageIndex * pageSize;
            int currentCount = 0;

            using (var reader = await command.ExecuteReaderAsync())
            {
                // 跳过不需要的行
                while (skipCount > 0 && await reader.ReadAsync())
                {
                    skipCount--;
                }
                // 读取分页数据
                while (currentCount < pageSize && await reader.ReadAsync())
                {
                    // 替换为你的实体字段映射逻辑
                    var item = new DbModels.DocumentWipList();
                    item.LimitedDoDiss = reader.GetString(reader.GetOrdinal("limited_dodiss"));
                    item.DocId = reader.GetInt32(reader.GetOrdinal("doc_id"));
                    // ...其他字段映射
                    result.Add(item);
                    currentCount++;
                }
            }
            return result;
        }
    }
}

关键说明

  • 方案1是数据库端分页,仅返回分页后的数据,是生产环境的首选,需注意PL/SQL中定义的记录类型要和存储过程返回的字段完全匹配。
  • 方案2本质是内存分页,依然会传输百万级数据,只适合临时场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 03:20:17