必须显式写ORDER BY,因为PostgreSQL不保证无序查询的行序,同一LIMIT/OFFSET查询多次执行可能返回不同结果;ORDER BY id最安全,但重复值需补唯一字段如id消歧,且OFFSET越大性能越差。

LIMIT 和 OFFSET 在 PostgreSQL 16 中仍是最直接的分页语法,但必须配合 ORDER BY 才能保证结果稳定。不加排序、或排序字段无索引,分页会出错或变慢。
为什么必须显式写 ORDER BY?
PostgreSQL 不保证无 ORDER BY 查询的行序——哪怕表有主键、哪怕刚插入完数据。同一查询执行两次,LIMIT 10 OFFSET 100 可能返回不同 10 行。
-
ORDER BY id是最常见且安全的选择,前提是id是主键或有唯一索引 - 如果排序字段存在重复值(如多个用户同秒注册),需补一个唯一字段消歧:
ORDER BY created_at, id - 避免用函数排序:
ORDER BY LOWER(name)无法走普通索引,除非你建了对应的函数索引
LIMIT 和 OFFSET 的参数顺序不能颠倒
PostgreSQL 16 支持 OFFSET m LIMIT n 语法(兼容旧版),但这是非标准写法,容易引发误解和维护风险。
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
- ✅ 正确且推荐:
LIMIT 20 OFFSET 100 - ⚠️ 能运行但不建议:
OFFSET 100 LIMIT 20 - ❌ 错误写法:
LIMIT 20, 100—— 这是 MySQL 语法,PostgreSQL 直接报错ERROR: syntax error at or near ","
OFFSET 越大,性能越差,不是“慢一点”,而是线性退化
当 OFFSET 达到十万级,查询延迟会明显上升,因为 PostgreSQL 必须真实扫描并丢弃前 N 行,无论这些行是否命中索引。
-
EXPLAIN ANALYZE中看到Rows Removed by Filter高得离谱,就是典型信号 - 即使
id有 B-tree 索引,OFFSET 1000000仍要跳过一百万行,无法跳过 - 小数据集(总行数 LIMIT/OFFSET 仍可接受
- 深分页(如第 500 页起)应改用游标分页:
WHERE id > 123456 ORDER BY id LIMIT 20
MyBatis 或其他 ORM 中计算 OFFSET 容易算错
常见错误是把页码当成从 0 开始,或混淆了“第 N 页”和“跳过 N 条”的关系。
- 第 1 页(每页 20 条):
OFFSET 0 - 第 2 页:
OFFSET 20,不是OFFSET 2 - 通用公式:
OFFSET = (page_number - 1) * page_size - MyBatis 示例:
LIMIT #{pageSize} OFFSET #{(pageNum - 1) * pageSize} - 注意:
pageNum必须校验 ≥ 1,否则OFFSET -20会报错
真正难的不是写对语法,而是判断什么时候该停用 OFFSET。只要业务允许“下一页/上一页”而非“跳转到第 N 页”,游标分页就该成为默认选项;而 LIMIT/OFFSET 更适合作为开发初期的快速验证手段,不是生产环境的长期方案。

















