抱歉,您的瀏覽器無法訪問本站
本頁面需要瀏覽器支持(啟用)JavaScript
了解詳情 >

前言

最近在研究知乎和掘金API时,发现他们的下滑加载用的并不是我们传统的页码分页,即page-size或者/offset-limit分页方式,而是一种参数为cursor-limit的分页方式,这是什么来的?它和我们常用的页码分页有什么区别?有什么优缺点?带着这些问题开始学习~

image-20231230001131643

掘金API分页方式

首先以掘金的最新文章APi为例,每当我加载更多文章时,cursor值就会发生微小的改变,观察一下就能看出cursor的值是经过base64编码过的。

1
2
3
4
5
6
7
8
9
10
11
12
13
curl --location '<https://api.juejin.cn/recommend_api/v1/article/recommend_all_feed>' \\
--header 'authority: api.juejin.cn' \\
--header 'accept: */*' \\
--header 'accept-language: zh-CN,zh;q=0.9' \\
--header 'content-type: application/json' \\
--header 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' \\
--data '{
"id_type": 2,
"client_type": 2608,
"sort_type": 300,
"cursor": "eyJ2IjoiNzMxNzI1MjM2ODU0Njc1ODY5NiIsImkiOjIwfQ==",
"limit": 20
}'

让我们解码一下,就可以看到一个json字符串,这里的v值便是某次请求时第一篇文章的id主键,i便是我们平常数据库中的offset。

1
{"v":"7317252368546758696","i":20}

我们每次请求后,响应体里总会返回一个的游标值,作为下一次加载更多文章的时的参数,随着每次滚动,i值就会不断累加,同时当i值过大时,v值的id也会发生改变,变为上次请求的第一篇文章的id,i值重新变为20。

1
2
3
4
5
...
"cursor": "eyJ2IjoiNzMxNzQ2MzcwMDQxNDcwOTc2MCIsImkiOjQwfQ==",
"count": 148008,
"has_more": true
}

基于游标分页方式

Google了一下就知道掘金API这种分页方式被称为游标分页(Cursor-based Pagination),它并不是像传统基于页码方式,而是基于记录本身的一个唯一标识(游标),来获取下一批数据。

我们先来回忆一下基于页码分页的缺点

以MySQL为例,当分页过深、即offset的值过大时,读取数据也就越耗时,因为数据库需要读取前面的行并跳过。

同时在并发场景下会出现读取的数据重复,例如当读取第二页时,第一页的数据新增或者删除,都u有可能影响到第二页的数据读取。

那么基于游标方式分页呢?

我们通过指定ID的方式,就可以告诉数据库从哪里开始直接读取,就可以避免多余的跳过操作,提高数据库的读取效率,同时因为指定了ID能够减少因数据变动导致的数据重复或遗漏问题。

但缺点也很明显,就是不能跳页了,用户并不能直接跳到数据列表中的任意位置。

那么什么时候用页码和游标的分页方式呢?

我觉得还是要看具体的业务场景,还是以知乎和掘金API为例,可以看到他们本身业务场景就没有总页数和分页跳转的说法,加载数据都是不断下滑来实现的,因此游标分页方式来加载数据就很适合了。

游标分页方式有很多具体的实现,知乎的游标分页就直接将下一页的HTTP地址直接返回,或者简单一点直接返回对应主键的ID等。最终用SQL来表示都是如下格式

1
SELECT id, data FROM items WHERE id > ? ORDER BY id ASC LIMIT ?

小结

  1. 页码分页适合数据量较小,记录变动不频繁的场景,用户可以方便地跳转到数据集的任意位置
  2. 游标分页适用于大型、动态变化的数据集,尤其是在数据实时更新和用户需要顺序遍历数据时

当然啦,使用什么技术方案还是要看具体的业务场景,在实际业务中有很多数据量很大,还是使用页码分页的情况,这就需要其他方式来优化了~