ROWNUM分页必须三层嵌套,因ROWNUM从1开始动态分配,WHERE ROWNUM>20恒不成立;正确做法:内层排序,中层取ROWNUM≤N,外层过滤rnum>起始值。
ROWNUM分页必须用三层嵌套,不能在WHERE里直接写ROWNUM > 20
oracle的rownum是查询执行时动态分配的伪列,从1开始、边取数据边编号。一旦你写where rownum > 20,第一行进来被标为1,不满足条件被丢弃;第二行进来又被标为1,还是不满足……永远查不到结果。
正确做法是:最内层排序(确保逻辑顺序),中间层用ROWNUM 截断物理读取量,外层再用别名列过滤起始位置。例如查第21–30条:
SELECT * FROM (
SELECT a.*, ROWNUM rnum FROM (
SELECT * FROM employees ORDER BY employee_id
) a WHERE ROWNUM <= 30
) WHERE rnum > 20- 中间层的
ROWNUM 能被CBO下推,Oracle实际只扫描前30行就停,性能可控 - 外层的
rnum > 20作用在已带序号的结果集上,安全有效 - 如果漏掉中间层限制,比如写成
WHERE ROWNUM BETWEEN 21 AND 30,Oracle会全表扫描再过滤,海量数据下极慢
ORDER BY必须放在最内层子查询里,否则ROWNUM序号错乱
ROWNUM按数据返回的物理顺序编号,和最终显示顺序无关。如果你把ORDER BY放在外层或中间层,排序发生在ROWNUM分配之后,得到的“第21–30行”根本不是按业务字段排好序后的中间段。
典型错误写法:
SELECT * FROM (
SELECT a.*, ROWNUM rnum FROM (
SELECT * FROM employees -- ❌ 这里没ORDER BY
) a WHERE ROWNUM <= 30
) WHERE rnum > 20
ORDER BY employee_id; -- ❌ 排序太晚,rnum已固定正确结构强制ORDER BY在最内层:
立即学习“Java免费学习笔记(深入)”;
SELECT * FROM (
SELECT a.*, ROWNUM rnum FROM (
SELECT * FROM employees ORDER BY employee_id -- ✅ 保证排序优先
) a WHERE ROWNUM <= 30
) WHERE rnum > 20- 没有索引支撑的
ORDER BY会导致大量临时表空间使用,务必确认employee_id等排序字段有索引 - 如果排序字段允许NULL,
ORDER BY column NULLS LAST要显式声明,避免NULL排在前面干扰分页边界
Java拼SQL时参数顺序别搞反:startRow和endRow不是页码×每页数
很多人习惯传pageNum=2, pageSize=10,然后算startRow = (pageNum-1)*pageSize,但ROWNUM分页真正需要的是绝对行号范围:WHERE rnum > 10 AND ROWNUM 对应第2页——注意这里是<code>> 10,不是>= 11,因为rnum是别名,且从1开始连续。
- 第1页:用
WHERE rnum > 0 AND ROWNUM (等价于<code>ROWNUM ) - 第2页:用
WHERE rnum > 10 AND ROWNUM - 第n页:
startRow = (n-1) * pageSize,endRow = n * pageSize,但SQL里写成rnum > startRow和ROWNUM - JDBC中建议用
PreparedStatement绑定参数,避免字符串拼接SQL引发SQL注入或空格错误
千万级数据慎用ROW_NUMBER(),OFFSET FETCH仅限12c+
ROW_NUMBER() OVER (ORDER BY ...)写法看起来直观,但它是先给全表打序号再过滤,对上亿数据意味着巨大临时表开销,比三层ROWNUM慢一个数量级。
OFFSET FETCH语法虽简洁:
SELECT * FROM employees ORDER BY employee_id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
但它要求Oracle版本≥12c,且某些旧驱动(如ojdbc6)不识别该语法,运行时报ORA-00933: SQL command not properly ended。
- 生产环境若无法升级数据库或驱动,
ROWNUM三层嵌套仍是唯一稳妥选择 - 用
ROWID分页(先取ROWID再回表)适合只查主键+少量字段的场景,但代码复杂度高,容易出错 - 真正卡顿往往不在SQL本身,而在JDBC fetch size设置过小,默认值10会导致多次网络往返,建议设为
statement.setFetchSize(50)


















