Oracle 12c+中OFFSET FETCH不能直接拼接PL/SQL变量,否则引发硬解析暴增、索引失效和共享池争用;必须使用绑定变量(如:offset)配合USING传参,且OFFSET/FETCH值须为纯数字,不可为表达式。
offset fetch 在 oracle 12c+ 里不是“能用就行”,而是“用错就崩”——直接拼变量、缺索引、传错类型,三者任一都会让分页从毫秒变秒级,甚至拖垮共享池。
OFFSET FETCH 语句里为什么不能直接写 PL/SQL 变量
在 PL/SQL 块中写 OFFSET v_offset ROWS 这种形式,Oracle 会当作字面量处理,导致每次 v_offset 值不同,就生成一个新执行计划。这不是慢一点的问题,是硬解析暴增、library cache latch 争用、索引扫描路径被绕过。
- 常见错误现象:
ORA-00933: SQL command not properly ended(字符串拼接漏空格或换行)、执行计划里出现FULL TABLE SCAN或SORT ORDER BY STOPKEY(说明排序没走索引) - 必须用绑定变量:写成
OFFSET :offset ROWS,再通过OPEN c USING v_offset或EXECUTE IMMEDIATE ... USING v_offset, v_size传入 -
OFFSET和FETCH NEXT后面的值不能是表达式,比如OFFSET (v_page - 1) * v_size ROWS是非法的,必须提前算好再传
Java JDBC 中调用 OFFSET FETCH 的典型陷阱
JDBC 层最容易忽略的是参数类型和校验逻辑,Oracle 对绑定变量类型敏感,而驱动又常做隐式转换。
- 用
PreparedStatement.setLong()设置OFFSET参数,别用setInt()——超大页码(如第 50 万页)时int溢出,触发隐式转NUMBER,执行计划失效 - Service 层必须校验
page和size:比如if (page 500)就抛异常,防止OFFSET被设成负数或几百万 - Spring Data JPA 默认生成
OFFSET/FETCH,但若底层表没建对索引,第 100 页可能耗时 3 秒以上,此时不是框架问题,是索引缺失
ORDER BY 字段没索引或顺序不匹配,性能直接归零
OFFSET 10000 ROWS FETCH NEXT 20 ROWS ONLY 不是跳过前 10000 行,而是逐行扫描、计数、丢弃,直到找到第 10001 行。这个过程完全依赖排序是否能复用索引。
- 复合索引字段顺序必须和
ORDER BY子句**完全一致**,例如ORDER BY status DESC, created_time ASC,对应索引必须是CREATE INDEX idx ON t (status DESC, created_time ASC) - WHERE 条件字段建议放在索引最左,比如
WHERE status = 'ACTIVE',就把status放索引第一位,避免回表 - 升序(
ASC)可省略,但降序(DESC)必须显式写出,否则 Oracle 不认为该索引可用于降序扫描
什么时候该放弃 OFFSET FETCH 改用游标分页
不是所有分页都适合 OFFSET/FETCH。当 OFFSET 超过 10 万,或者业务允许“下一页”但不支持“跳转任意页”时,游标分页更稳。
- 典型场景:消息流、订单列表、审计日志——用户只看最新一批,且按时间倒序;这时用
WHERE created_time +ORDER BY created_time DESC,配合索引,性能几乎恒定 - 游标值必须是唯一、单调、非空的字段(如
id或created_time),不能用name或status这类重复值字段做游标 - 注意时区与精度:如果用
DATE类型做游标,要确保应用层和数据库时区一致,否则同毫秒内多条记录可能漏掉
真正卡住性能的,往往不是语法本身,而是 OFFSET 值传进来时没校验、ORDER BY 字段没索引、或者 PL/SQL 里图省事拼了字符串——这些地方一松懈,执行计划就跑偏,而且很难一眼看出来。











