分页(Pagination)原指网页/图集底部「第几页」的序数编号,把内容拆成一段段。API 分页就是把这个原则用到接口设计上:面对可能返回百万甚至上亿条结果的密集查询,如果不加限制,对 API 的带宽与算力都是无底洞。分页的作用,就是把返回结果的数量限制住,让网络流量保持在可控范围。
大数据时代,一次查询可能返回上亿条结果,全量返回既拖慢响应又压垮带宽。API 分页是控制返回规模、保障性能的关键手段。本文编译自 Nordic APIs(2019-10-17 首发,2023-05-30 更新),讲清分页是什么,对比 Offset / Keyset / Seek 三种主流方案的优劣,并给出 REST API 中接入分页链接与 Spotify 实战。
分页(Pagination)原指网页/图集底部「第几页」的序数编号,把内容拆成一段段。API 分页就是把这个原则用到接口设计上:面对可能返回百万甚至上亿条结果的密集查询,如果不加限制,对 API 的带宽与算力都是无底洞。分页的作用,就是把返回结果的数量限制住,让网络流量保持在可控范围。
Offset 分页是最简单的实现:用 limit 和 offset 两个参数。它在 SQL 系应用里特别流行,因为 SQL 的 SELECT 本身就带 LIMIT 和 OFFSET。
GET /items?limit=20&offset=100
优点:几乎零编码;服务端无状态;配合自定义 sort_by 也能用。缺点:① 偏移值很大时性能差——offset=1000000 意味数据库要先扫过 100 万行再丢弃;② 新增数据会引发页面漂移(page drift):先查 offset=0&limit=15,插入 10 条后再查同样的参数,只会返回 5 条(因为新数据把偏移顶回了 10 位),客户端一脸懵。
Keyset 分页用「上一页最后一个结果的过滤值」来定位下一页。比如:先取最新 20 条 GET /items?limit=20,翻页时用上一页最小创建时间做过滤器 GET /items?limit=20&created:lte:2019-01-20T00:00:00。
优点:不需要额外后端逻辑,只需一个 limit 参数;顺序稳定——即便中途新增数据,排序也不乱;深翻页同样流畅。代价是它和具体的过滤字段耦合,客户端得知道那个键值。
Seek 是 Keyset 的进阶版:引入 after_id / before_id,把「过滤条件」与「分页」解耦,用更稳定的唯一标识翻页。例如 GET /items?limit=20&after_id=20 取下一页。它本质上就是一句 SELECT * FROM Items WHERE Id > 20 LIMIT 20。
优点:过滤逻辑与分页逻辑解耦;顺序稳定;深翻页流畅。缺点:① 后端资源消耗比 Offset/Keyset 略高;② 若某条数据被删,after_id 指向的 id 可能已失效。注意:Seek 难以做自定义排序——比如想按邮箱排序,后端得先查邮箱对应 id,再拿 id 当 WHERE 值二次查询,多一趟往返。
分页不只是后端逻辑,还要让客户端「能逛」。推荐用 HAL(Hypertext Application Language)让 API 可发现:在响应里带 _links,包含 self / first / prev / next / last。这几个名字被广大 API 开发者通用,用它们能让你的接口和其他 API 保持一致。
示例响应片段:"_links": {"self":".../user?page=3","first":".../user","prev":".../user?page=2","next":".../user?page=4","last":".../user?page=133"},再附 count 与 total。借助 HATEOAS 库(如 PHP 的 PaginatedRepresentation),把 page/limit 当作查询参数,即可快速产出带翻页链接的列表接口。
公开 API 普遍采用分页。以 Spotify 取某艺人专辑为例:GET /v1/artists/{id}/albums?include_groups=album&limit=10 用 limit 限制返回 10 张;再用 offset=10 取接下来 10 张。这种「limit + offset」组合简单直观,是 Web API 分页的范本——参考它来设计你的服务,用户就能在复杂数据上顺畅翻页。
分页随 API 复杂度上升只会更重要。三种主流方案:Offset 最简单,但有深翻页性能差、页面漂移的短板;Keyset 更稳健,却与页面结果强耦合;Seek 最稳健(新增数据顺序仍稳定),但后端实现更复杂、删数据可能错位。
经验法则:数据量小、需随意跳页时用 Offset;追求顺序稳定与性能用 Keyset/Seek;分页链接务必遵循 self/first/prev/next/last 约定,并配合文档讲清每个参数的含义与边界情况。