limit和offset仅适合小数据量分页,因offset需真实扫描并丢弃前n行,导致性能随偏移量线性下降;必须配合order by保证结果稳定,且排序字段应唯一或组合消歧,大偏移场景应改用游标分页。

LIMIT 和 OFFSET 在 PostgreSQL 15 中能直接分页,但仅适合小数据量或浅层翻页;一旦 OFFSET 超过几万,查询会明显变慢——这不是配置问题,是执行机制决定的。
ORDER BY 是硬性前提,不是建议
没有 ORDER BY 的分页结果不可预测,哪怕表有主键、数据插入顺序固定。PostgreSQL 不保证无排序时的行返回顺序,同一语句多次执行可能跳过不同记录,导致漏数或重复。
- 错误写法:
SELECT * FROM users LIMIT 20 OFFSET 40(缺ORDER BY) - 正确写法:
SELECT * FROM users ORDER BY id LIMIT 20 OFFSET 40 - 若排序字段存在重复值(如多个用户同秒注册),需补唯一字段消歧:
ORDER BY created_at, id - 避免在
ORDER BY中用函数,如ORDER BY LOWER(name),除非已建对应函数索引,否则无法走索引
LIMIT 和 OFFSET 的位置与参数计算
PostgreSQL 15 支持 LIMIT n OFFSET m(标准写法)和 OFFSET m LIMIT n(兼容写法),但后者易引发协作误解,不推荐。
-
OFFSET值 =(当前页码 - 1) * 每页条数,例如第 5 页、每页 20 条 →OFFSET 80 -
OFFSET 0可省略:LIMIT 20等价于LIMIT 20 OFFSET 0 - 不要手算错位:第 1 页是
OFFSET 0,不是OFFSET 1;页码从 1 开始,偏移从 0 开始
OFFSET 越大越慢的根本原因
PostgreSQL 必须真实扫描并丢弃前 OFFSET 行,即使走了索引,也要逐行判断可见性(MVCC 可见性检查),无法跳过。
- 现象:
EXPLAIN ANALYZE显示Rows Removed by Filter高得离谱,或执行时间随OFFSET线性增长 - 举例:
OFFSET 1000000 LIMIT 20会让数据库读取并丢弃前一百万行,只为了返回 20 条 - 索引是否覆盖、是否复合、是否唯一,都不改变这一开销本质
什么时候该换游标分页(键集分页)
当业务允许“下一页/上一页”但不需要“跳转到第 N 页”时,游标分页是唯一靠谱方案。
- 首次查第一页:
SELECT * FROM users ORDER BY id LIMIT 20,记下最后一条的id(比如 150000) - 下一页查:
SELECT * FROM users WHERE id > 150000 ORDER BY id LIMIT 20(必须WHERE和ORDER BY字段一致) - 确保
id字段有索引,且单调递增、无删改空洞(或接受空洞带来的“跳过”) - 前端必须传回上一页末尾的
id,不能只传页码;否则就退化回OFFSET陷阱
深分页不是调优能解决的问题,是范式选择问题。用 OFFSET 实现跳页管理后台,用游标支撑用户端无限滚动——两者共存很常见,但混用容易出错。










