导读:本期聚焦于小伙伴创作的《如何用Oracle存储过程实现高效的分页查询?实例代码与步骤详解》,敬请观看详情。在报表系统或后台管理界面中,当单表数据量超过百万行时,前端一次性拉取全部记录会让响应时间陡增。Oracle数据库提供了rownum伪列与游标结合的方式,能把分页逻辑下沉到存储过程里执行。本文给出一个可复用的分页存储过程模板,利用输入参数控制页码与每页大小,通过内层查询先圈定数据再外层过滤行号,避免客户端内存溢出。同时对比了使用rownum和offset fetch两种写法在12c前后版本中的差异,并说明如何通过输出参数返回总记录数,方便页面计算总页数。

在Oracle数据库中处理大量数据的分页需求时,把分页逻辑封装进存储过程是一种常见且高效的做法。它既能减少网络传输量,又能利用数据库自身的优化器提升查询性能。下面通过一个完整实例,说明如何编写可复用的Oracle分页存储过程。

如何用Oracle存储过程实现高效的分页查询?实例代码与步骤详解

一、分页的基本原理与rownum机制

Oracle中没有像MySQL那样直接的limit语法(12c之前),而是依靠名为rownum的伪列来实现行号控制。rownum是在查询结果集返回时按顺序分配的一个序号,从1开始。需要注意的是,rownum必须先被查询出来,然后才能在外层对其进行大于某值的过滤,否则无法命中索引或得到空结果。

因此最常见的分页写法是三层嵌套:最内层做排序和查询,中间层用rownum起别名并限制上限,最外层再过滤下限。这样数据库只需扫描到所需页的数据量,而不必全表加载。理解这一点是写出正确存储过程的前提。

1.1 为什么不能直接用where rownum between

很多初学者会尝试写select * from emp where rownum between 10 and 20,但这样永远查不到数据。因为rownum是在记录被选出后才编号,当第一条不满足between时,后续记录的rownum不会递增填补。必须用子查询把rownum固化成普通列,再在外层判断。

示例错误与正确逻辑对比如下,错误写法返回零行,正确写法通过内层先赋列名rno来解决:

-- 错误示例:永远无结果
select * from emp where rownum between 10 and 20;

-- 正确示例:三层嵌套
select * from (
  select e.*, rownum rno from (
    select * from emp order by empno
  ) e where rownum <= 20
) where rno >= 10;

二、创建分页存储过程实例

我们创建一个名为proc_paginate_emp的存储过程,接收表名、排序字段、页码、页大小,并输出当前页数据和总记录数。为简化演示,这里固定对emp表操作,实际中可用动态SQL扩展。

存储过程使用sys_refcursor作为输出游标,调用方(如Java或PL/SQL块)可直接遍历。同时用count(*)计算总数,通过out参数返回,避免前端再发一次统计请求。

2.1 存储过程代码

create or replace procedure proc_paginate_emp(
  p_page_no   in  number,
  p_page_size in  number,
  p_total     out number,
  p_cursor    out sys_refcursor
) as
  v_start number;
  v_end   number;
begin
  -- 计算起止行号
  v_start := (p_page_no - 1) * p_page_size + 1;
  v_end   := p_page_no * p_page_size;

  -- 查询总记录数
  select count(*) into p_total from emp;

  -- 打开游标返回当前页数据
  open p_cursor for
    select * from (
      select e.*, rownum rno from (
        select * from emp order by empno
      ) e where rownum <= v_end
    ) where rno >= v_start;
end proc_paginate_emp;

2.2 在PL/SQL中调用测试

下面代码演示如何声明变量并调用上述过程,打印出第二页每页五条的数据以及总数:

declare
  v_total number;
  v_cur   sys_refcursor;
  v_emp   emp%rowtype;
begin
  proc_paginate_emp(2, 5, v_total, v_cur);
  dbms_output.put_line('总记录数:' || v_total);
  loop
    fetch v_cur into v_emp;
    exit when v_cur%notfound;
    dbms_output.put_line(v_emp.empno || ' ' || v_emp.ename);
  end loop;
  close v_cur;
end;

三、Oracle 12c及以后版本的offset写法

从Oracle 12c开始,官方支持了ANSI标准的offset fetch语法,让分页变得更直观。如果数据库版本允许,可以改写游标部分,无需rownum嵌套。

这种写法可读性更好,优化器也能更容易地生成执行计划,但在旧版本中不可用。在存储过程中可通过条件编译或分开的过程来兼容。

3.1 使用offset fetch的游标

open p_cursor for
  select * from emp
  order by empno
  offset (p_page_no - 1) * p_page_size rows
  fetch next p_page_size rows only;

3.2 两种方案对比

方案适用版本可读性性能特点
rownum三层嵌套所有版本一般稳定,依赖内层排序索引
offset fetch12c及以上语义清晰,计划易优化

四、注意事项与优化建议

在真实项目中,排序字段应当有索引,否则内层查询会触发全表排序,分页优势尽失。另外,如果数据实时变化剧烈,count(*)得到的总数与当前页可能不一致,业务上需容忍或加快照。

对于超大数据表,可考虑利用主键范围分段代替通用分页,或者把总数缓存起来。存储过程里尽量使用绑定变量,防止硬解析过多。通过合理设计,Oracle存储过程分页能支撑高并发后台查询场景。

Oracle存储过程分页查询修改时间:2026-08-07 20:36:30

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。