Oracle OFFSET/FETCH 为何在不同偏移量下返回相同行?
结论:分页需要能唯一确定行顺序的排序条件
ORDER BY name 没有规定相同 name 的行之间如何排序,因此两次查询可能返回完全相同的 10 行。OFFSET 仍然分别跳过各自查询结果中的 1 行和 11 行,但并列行在两次查询中的顺序不保证一致。
对题目中的数据,改用 ORDER BY name, x 就可以确定顺序。Oracle 的 SELECT 文档也明确要求,使用行限制子句时,应通过排序条件确保结果顺序具有确定性。
为什么第 2 至 11 行与第 12 至 21 行可能相同?
“这两个区间不会重叠”的前提是,它们来自同一个顺序确定的序列。这里执行的是两条独立的 SQL,数据库没有承诺为它们维护同一份排序结果。
对于 5000 行 CDR,ORDER BY name 无法区分各行的先后。虽然 x 当前唯一,但它没有出现在 ORDER BY 中,Oracle 就没有义务用它区分并列行,也没有义务保持插入顺序。
假设五行的 name 都是 CDR,下面两种 x 顺序都满足 ORDER BY name:
执行 A:2, 4, 6, 8, 10
执行 B:2, 8, 10, 4, 6
执行 A 跳过 1 行、取 2 行,得到 4, 6;执行 B 跳过 3 行、取 2 行,也得到 4, 6。这个例子说明合法的排序结果如何产生重叠,不代表题目环境实际采用的处理过程。
因此,完全重复和部分重叠都可能发生。“不确定”也不表示结果每次都会随机变化。即使某个环境反复出现相同结果,也不能说明数据库保证了这个顺序。
内部机制:行限制可能参与排序优化
行限制不一定是在生成一份完整、稳定的排序结果后才执行。Oracle 可以把行限制转化为基于 ROW_NUMBER() 的处理,并将上界条件下推到排序阶段。
从逻辑上看,这两条查询的取行条件分别相当于:
OFFSET 1,FETCH 10: rn > 1 AND rn <= 11
OFFSET 11,FETCH 10: rn > 11 AND rn <= 21
其中 rn 按 name 排序编号。Oracle 文档说明,ROW_NUMBER() 遇到排序键相同的行时,仍会分配不同编号;如果排序条件不能确定唯一顺序,编号分配可能不确定。参见 Analytic Functions 文档。
如果执行时采用 Top-N 排序优化,上界从 11 变为 21,可能影响并列行的保留和输出次序。即使两条 SQL 的执行计划显示相同的算子名称,并列行的编号也不一定保持不变。Oracle 的行限制执行计划示例展示了 WINDOW SORT PUSHED RANK 算子,以及基于 ROW_NUMBER() 的上界过滤。
题目没有提供 Oracle 版本和执行计划,无法断言当前实例使用了哪种具体排序算法,也无法仅凭输出解释为什么恰好出现 1706 到 1688 这十个 x 值。原查询没有约束这些并列行之间的顺序。
修改查询:加入唯一的排序依据
题目生成的数据中,x 唯一,可以直接用它作为第二排序键:
SELECT *
FROM t
ORDER BY name, x
OFFSET 1 ROWS FETCH NEXT 10 ROWS ONLY;
SELECT *
FROM t
ORDER BY name, x
OFFSET 11 ROWS FETCH NEXT 10 ROWS ONLY;
在数据保持不变且 CDR 排在 SDS 前面的条件下,按数据定义可以推导出:
- 第一条查询返回 x 为
4, 6, 8, 10, 12, 14, 16, 18, 20, 22的行。 - 第二条查询返回 x 为
24, 26, 28, 30, 32, 34, 36, 38, 40, 42的行。
这些是根据排序规则推导的预期结果,并非实际执行测试的记录。
建表语句没有为 x 声明唯一约束,只是这次生成的数据恰好唯一。实际业务中,应使用主键或其他能保证排序键组合唯一的字段。
怎样检查当前环境的执行方式?
如果需要继续追查内部处理,可以分别在执行两条原始查询后,立即查看对应游标的执行计划:
SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'TYPICAL'));
这需要相应的动态性能视图访问权限,并应确保查看的是目标查询的游标。重点观察排序算子,以及 Predicate Information 中是否出现 ROW_NUMBER()、行号上界和 OFFSET 对应的下界过滤。
执行计划可以帮助确认当前实例采用的行限制路径,但通常不足以还原排序过程中每一次并列元素的交换。连续几次输出相同,也不能说明 Oracle 保证了该顺序。
分页时还要注意什么?
手动改写为 ROW_NUMBER() OVER (ORDER BY name) 仍然缺少唯一排序依据;如需这样改写,也应使用 ORDER BY name, x。
唯一排序解决的是并列行的顺序问题。如果两次分页查询之间发生插入、删除或排序字段更新,行的位置仍可能变化。需要跨页一致性时,还应结合业务要求控制读取的数据快照。
备注:内容仅供参考。
