传统分页和流式分页
流式分页应用场景:
通过滚动/上拉/点击等方式加载新一页
无页码
无上/下页按钮
不可跳转至指定页面
pc端和移动端均有使用
实现:
前端实现:后端返回全部结果,前端做分页
后端实现:
用offset和limit来实现(mysql)
优化:
当前页数据(及其之前)若有删除,由于计算问题total/limit*offset,数据整体上移(变少),指针相对下移,会出现数据缺失。
当前页数据(及其之前)若有增加,由于计算问题total/limit*offset,数据整体下移(变多),指针相对上移,会出现数据重复。
方案:
1. 后端方案,缓存数据
2. 后端方案,用cursor记录最后一个ID。这叫游标式分页。DynamoDB采用这个方案。pagination token带有过期时间戳。
3. 前端方案,一次性发所有Id,缓存到前端。
4. 前端方案,客户端保留已浏览数据,手动去重。不能避免数据缺失。
游标式分页
这个方案避免了数据缺失和重复,但有适用范围。它仅能用于追加式的单一排序。这要求
1. 单一排序:这个顺序是固定的,而且可以快速定位到这个ID。
2. 追加式:只能添加和删除一行,不能更新某些域。更新它们若影响排序的话,就涉及数据快照概念。若第一页分页请求产生时(方案一)生成时间戳,这个时间戳对应整套数据的快照,若某些域在这个时间戳之后更新(可以按行来添加lastUpdateTime),按照设计,应该显示旧值。这要求每个域所有更新都要有历史记录。此解决方案有利有弊:可以避免数据重复出现,但是新数据会不显示。
* 快照模式:用lastUpdatedTimestamp
* 若要实现scroll down时候,新加入的entries也显示,这不属于快照模式,可以用timestamp的UUID, uuid > last-entry-uuid就可以,缺点是符号条件的entry只能按插入时间顺序排序而不能按其他条件排序
MongoDB的游标式实现
Request:
// Query the first page.
let result = await MongoPaging.find(db.collection('myobjects'), {
limit: 2
});
console.log(result);
// Query next page.
result = MongoPaging.find(db.collection('myobjects'),
{
limit: 2,
next: result.next // This queries the next page
});
console.log(result);
}
Response:
page 1 { results:
[ { _id: 580fd16aca2a6b271562d8bb, counter: 4 },
{ _id: 580fd16aca2a6b271562d8ba, counter: 3 } ],
next: 'eyIkb2lkIjoiNTgwZmQxNmFjYTJhNmIyNzE1NjJkOGJhIn0',
hasNext: true }
page 2 {
results:
[ { _id: 580fd16aca2a6b271562d8b9, counter: 2 },
{ _id: 580fd16aca2a6b271562d8b8, counter: 1 } ],
previous: 'eyIkb2lkIjoiNTgwZmQxNmFjYTJhNmIyNzE1NjJkOGI5In0',
next: 'eyIkb2lkIjoiNTgwZmQxNmFjYTJhNmIyNzE1NjJkOGI4In0',
hasNext: false }
Request中的limit的返回结果的大小,next上改页结果的最后一项的id,也就是下一页结果需要从这个项目后开始查找。
Response中的token是 {_id: 123} 的base64 encode. 比如page 1的next就是第二个(最后一个)id的encode。hasNext的设计可以让客户更清晰地知道是否有下一页,当然也可以用next=''来替代。
在有些设计中,token加入timestamp, {_id: 123, timestamp: 2019-06-21 12:23:11},设一个SLA为24小时,也就是下一个翻页请求在SLA内完成,这样可以减少不必要的请求。
Ref
[1]
https://aotu.io/notes/2017/06/27/infinite-scrolling/index.html
[2]
MongoDB实现
[3]
游标式分页的优势