
大家好,我是Tony Bai。
欢迎来到我们的专栏 《API 设计之道:从设计模式到 Gin 工程化实现》的第五讲。
在上一讲中,我们通过“字段掩码”解决了单条数据过大的传输效率问题。今天,我们把视角拉远,看看当数据量成千上万时,API 该如何高效地传输列表数据。
在早期的 Web 开发中,每当我们需要实现一个“用户列表”或“订单列表”接口时,脑海中浮现的 SQL 往往是这样的:
SELECT * FROM users ORDER BY created_at DESC LIMIT 10 OFFSET 1000;
对应的 API 参数通常是 ?page=100&page_size=10。
这种基于偏移量(Offset-based)的分页方式,在数据量较小时(比如几千条)运行良好,且非常直观:用户想看第几页就跳到第几页。
但在云原生和海量数据时代,这种设计就像一颗定时炸弹。当数据量达到百万、千万级别时,DBA 会拿着慢查询日志找上门来;当用户在瀑布流页面(如抖音、Twitter)下拉刷新时,他们会发现数据重复出现或莫名丢失。
为什么最经典的 Offset 分页如今成了反模式?Google、Facebook、Twitter 的 API 为什么不再支持 page 参数,而是强制使用 next_page_token?
今天这一讲,我们就来彻底拆解分页的架构设计,并用 Go 实现一套高性能的游标分页(Cursor-based Pagination)机制。
痛点一:Offset 的性能塌陷
让我们复习一下 MySQL 的工作原理。当你执行 LIMIT 10 OFFSET 1000000 时,数据库并不是直接“跳”到第 100万 行。
数据库必须先扫描前 1,000,000 行数据,然后把它们扔掉,最后才返回接下来的 10 行。
这就好比你让一个人吃苹果,他想吃第 100 个。用 Offset 模式,他必须先把前 99 个苹果削皮、切块、拿起来,然后再扔进垃圾桶,最后才吃第 100 个。
随着 OFFSET 值的增加,查询时间是呈线性增长的。这就是著名的 Deep Pagination(深度分页) 性能问题。
痛点二:数据漂移 (Data Drift)
性能慢还能忍,但数据不一致则是严重的业务 Bug。
想象一个新闻 App 的场景:
用户打开 App,加载了第 1 页(最新的 10 条新闻)。
在用户阅读期间,后台编辑又发布了 5 条新新闻。
用户看完当前页,上滑加载第 2 页 (
OFFSET 10)。
此时会发生什么?
由于新插入了 5 条数据,原本第 1 页的后 5 条数据,现在被“挤”到了第 2 页的位置。
结果:用户在第 2 页看到了刚刚在第 1 页已经看过的 5 条新闻。
同理,如果有数据被删除,用户在翻页时就会漏掉数据。对于追求极致体验的移动端应用,这是不可接受的。
966

被折叠的 条评论
为什么被折叠?



