SQL 分页
SQL 分页的核心不是会写 LIMIT/OFFSET,而是保证顺序稳定、理解大偏移成本,并知道何时需要游标式分页。
[!info] related notes
- 所属 MOC: 数据库 MOC
- 前置概念: SQL, 查询, RESTful API
- 相关概念: 数据库索引, SQL 查询执行流程, 前端列表页状态处理
- 相关主题: 中国移动浙江金华系统开发工程师(Web端)春招复试准备
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适合普通翻页- 深分页要警惕大偏移成本
- 无限滚动常考虑游标式分页