SQL 分页

SQL 分页的核心不是会写 LIMIT/OFFSET,而是保证顺序稳定、理解大偏移成本,并知道何时需要游标式分页。

#type / concept #status / growing #tech / dev / backend #resource / sql #resource / database

[!info] related notes

SQL 分页

一句话定义

SQL 分页就是把大结果集按顺序切成一页一页返回,但真正的关键在于顺序稳定和深分页成本。

为什么分页不是只会写 LIMIT

业务里的分页通常同时包含三个问题:

  • 这一页取哪几条
  • 按什么顺序取
  • 总共有多少条

如果只会写 LIMIT 10 OFFSET 20,但没想清楚排序和性能,实际接口就很容易出问题。

最常见写法:LIMIT + OFFSET

SELECT id, name, created_at
FROM users
ORDER BY created_at DESC, id DESC
LIMIT 10 OFFSET 20;

这表示:

  • 先按创建时间倒序排
  • 再跳过前 20 条
  • 取接下来的 10 条

如果按 10 条一页算,这大致就是第 3 页。

为什么必须有稳定排序

分页最怕的是“同一条数据一会儿在这一页,一会儿跑到下一页”。

所以分页查询不能只有 LIMIT/OFFSET,还要有稳定的 ORDER BY

常见做法是:

  • 先按业务主排序字段排
  • 再补一个唯一字段兜底,比如 id

例如:

ORDER BY created_at DESC, id DESC

这样即使多条记录 created_at 一样,顺序也不会漂。

总数怎么拿

列表接口经常会同时返回:

  • 当前页数据
  • total

常见方式是再跑一条 COUNT(*)

SELECT COUNT(*)
FROM users;

但工程上要知道:

  • total 不是永远都必须
  • 大表下 COUNT(*) 也可能有成本

所以有些场景会:

  • 首次加载给总数
  • 无限滚动不返回精确总数
  • 只返回“是否还有下一页”

深分页为什么会慢

OFFSET 越大,数据库通常需要先跳过越多记录。

例如:

SELECT id, name
FROM users
ORDER BY id
LIMIT 10 OFFSET 100000;

这不是说数据库能瞬移到第 100001 条,而往往意味着它要先处理前面大量记录,再把后 10 条返回。

所以深分页常见问题就是:

  • 页码越往后越慢
  • 大偏移下查询代价明显上升

游标式分页 / Keyset Pagination

当列表非常大,或者无限滚动很深时,常改成“基于上一条记录”的分页。

例如:

SELECT id, name, created_at
FROM users
WHERE (created_at, id) < ('2026-05-01 10:00:00', 1200)
ORDER BY created_at DESC, id DESC
LIMIT 10;

这类思路不是按“第几页”取,而是按“上一页最后一条之后继续取”。

它更适合:

  • 时间流
  • 消息流
  • 无限滚动
  • 很深的翻页

最容易错的地方

1. 没有 ORDER BY

没有稳定排序的分页,在数据变化时结果会漂移,严格说不算可靠分页。

2. 只知道页码,不知道深分页成本

很多人能写分页参数,却说不清为什么第 10000 页会慢。这背后就是大偏移跳过成本。

3. 忽略前端的实际体验

分页不是数据库单方面的事,还会直接影响前端:

  • 列表刷新时要不要保留旧数据
  • 是否展示总数
  • 是传统翻页还是无限滚动

所以分页方案经常要和接口设计、前端交互一起考虑。

面试里可以直接说的版本

“最常见的 SQL 分页是 LIMIT + OFFSET,但我会先保证 ORDER BY 稳定,否则翻页会乱。大偏移会导致深分页变慢,所以数据量很大或无限滚动场景下,通常会考虑基于游标或最后一条记录的分页方式。”

最短记忆方式

  • 分页先想顺序,再想页码
  • LIMIT/OFFSET 适合普通翻页
  • 深分页要警惕大偏移成本
  • 无限滚动常考虑游标式分页
创建于 2026/5/7 更新于 2026/5/27