因为postgresql必须真实扫描并丢弃前n行,即使有b-tree索引也无法跳过,导致i/o与cpu开销随offset线性增长;排序字段重复还会引发分页结果不稳定。

为什么OFFSET越大,查询越慢?
因为PostgreSQL必须真实扫描并丢弃前N行——哪怕id有B-tree索引,OFFSET 1000000也要跳过一百万行,无法跳过。执行计划里看到Rows Removed by Filter极高,就是典型信号。实测中,OFFSET 100000耗时约200ms,OFFSET 1000000可能超2秒,且随偏移线性退化。
ORDER BY不加唯一字段会出什么问题?
排序字段存在重复值(比如多个订单同秒创建),会导致分页结果不稳定:同一LIMIT 20 OFFSET 40执行两次可能返回不同行,甚至出现重复或遗漏。解决方法是补一个唯一字段消歧:
-
ORDER BY created_at, id(推荐,id为主键) - 避免用函数排序,如
ORDER BY LOWER(name)——除非你建了函数索引 - 绝不省略
ORDER BY:PostgreSQL不保证无序查询的行序,哪怕表刚插入完、有主键也不行
什么时候该停用OFFSET?
真正难的不是写对语法,而是判断切换时机。以下情况必须换方案:
- 业务允许“下一页/上一页”但不要求“跳转第N页” → 直接用游标分页:
WHERE id > 123456 ORDER BY id LIMIT 20 - 总行数超百万、OFFSET ≥ 10万 → 即使加了索引也扛不住,延迟会明显上升
- MyBatis等ORM计算
OFFSET容易错:确认pageNum校验≥1,公式为(pageNum - 1) * pageSize,别把页码当从0开始
游标分页依赖排序字段单调、高选择性(如主键或带时序唯一性的created_at),且需缓存上一页末尾值;它不是“更高级的OFFSET”,而是彻底绕开位置依赖的另一种范式。











