# LIMIT 与 OFFSET

> 限制结果大小，并比较偏移量分页与键集分页。

---

LLMS 索引： [llms.txt](/llms.txt)

---

`LIMIT n` 至多返回 `n` 行。`OFFSET m` 跳过结果中的前 `m` 行。两者结合，就能实现传统的按页码分页：每页 `p` 行的第 `k` 页写作 `LIMIT p OFFSET (k - 1) * p`。没有 `ORDER BY`，“前”就没有意义；使用 `LIMIT`/`OFFSET` 时，一定要明确指定顺序。

## 本章要点 {#learn}

- 用 `LIMIT` 限制结果大小
- 用 `OFFSET` 跳过行
- 分页方式及其代价
- 为什么偏移量分页在大表上难以扩展
- 更好的替代方式：键集分页（也称游标分页）

## 示例 {#example}

```sql {num="7.1" caption="LIMIT 与 OFFSET"}
-- First two rows, ordered by id
SELECT * FROM fruits ORDER BY id LIMIT 2;

-- Skip one, then take two: rows 2 and 3
SELECT * FROM fruits ORDER BY id LIMIT 2 OFFSET 1;

-- Page 2 of 3 per page
SELECT * FROM fruits ORDER BY id LIMIT 3 OFFSET 3;

-- Keyset pagination: rows with id > last_seen_id
SELECT * FROM fruits WHERE id > 2 ORDER BY id LIMIT 2;
```

`LIMIT 2 OFFSET 1` 可以读作“跳过一行，再取两行”。在内部，PostgreSQL 仍然必须计算直到 `OFFSET + LIMIT` 为止的所有行，再丢弃偏移量对应的那些行。如果页码从 1 开始，每页 50 行，获取第 10,000 页就意味着要处理 500,000 行。偏移量分页用于前几页还不错，但用于深度分页就很糟糕。

键集分页（也称*游标分页*或 *seek 分页*）使用上次看到的值作为过滤条件：`WHERE id > $last_id ORDER BY id LIMIT 50`。无论已经翻到多后面的页，查询都只访问接下来的 50 行。这需要有索引的列（通常是主键，或 `created_at, id`），但也是生产系统中的标准做法。

## 运行示例 {#run}

运行方式（需要前面章节的 `fruits` 表）：

```text
$ psql -f source/limit-offset.sql postgres
 id |  name
----+--------
  1 | apple
  2 | banana
(2 rows)

 id |  name
----+--------
  2 | banana
  3 | cherry
(2 rows)
 ...
```

## 常见陷阱 {#pitfalls}

- 没有 `ORDER BY` 的 `LIMIT 10` 返回的十行只是*某些*行，每次执行选中的行还可能不同。一定要排序。
- 排序键相同的两行可能在不同页之间交换位置。增加一个用于打破并列的排序键（`ORDER BY created_at DESC, id DESC`），让分页保持稳定。
- 很大的偏移量会很慢。对于“无限滚动”信息流的第 1,000 页，应改用键集分页。
- `OFFSET` 也会出现在 `SELECT ... FOR UPDATE` 这类用法中，与行锁配合使用时可能产生问题；如果需要分批处理，基于主键的键集分页同样是更安全的方式。

> [!TIP] 提示
> PostgreSQL 也支持 SQL 标准写法 `FETCH FIRST n ROWS ONLY`，它等价于 `LIMIT n`。如果边界处存在并列，`FETCH FIRST n ROWS WITH TIES` 返回的行数就会超过 `n`，很适合 top-N 查询。

> [!NOTE] 动手练习
> 求出“第三大的水果 id”：`SELECT id FROM fruits ORDER BY id DESC LIMIT 1 OFFSET 2;`。然后尝试 `OFFSET 0`、`OFFSET 1` 等。再改写成键集分页：`SELECT id FROM fruits WHERE id < (some_value) ORDER BY id DESC LIMIT 1`。

## 下载 {#downloads}

SQL 文件： [limit-offset.sql](/examples/source/limit-offset.sql) · [全部示例文件](/examples/examples.zip) · [准备工作](/#prerequisites)
