两种分页方式
传统的分页方式页最典型的特点是页面上有一连串的页码,和电梯按钮相似,因此页常被称之为电梯式分页。
电梯式特点:
- 通过页码进行分页
- 通过点击上/下页按钮可实现页面切换
- 通过点击页码可实现页面切换
- 可直接跳转至指定页面
- 多用于 PC 端,适合需要查找特定内容的页面
- 需要计算总数or总页数(搜索引擎等场景也可以无需计算,相应的跳转按钮会有所限制)
电梯式分页适用于传统的页面布局,而在移动端页面上更流行的是瀑布流布局方式,相应的分页方式称为流式分页,有时也称为无限下拉式分页。
流式分页特点:
- 通过滚动/上拉/点击等方式加载新一页
- 无页码
- 无上/下页按钮
- 不可跳转至指定页面
- pc端和移动端均有使用,适合UGC、视觉内容以及推荐系统等“浏览型”页面
- 无需计算总数or总页数
对于用电梯式分页方式的接口,有页码、页大小以及结果总数等参数,页码需要明确是从1开始还是0开始,一般使用pageNumber表示从1开始,使用pageIndex表示页索引从0开始,pageSize和limint均可表示单页数据量。totalCount和totalPage可以分别表示总数据条数和总页数。
- page(pageNumber、pageIndex)
- pageSize/limit
- total(totalCount、totalPage)
当然,对于流式分页,上述的接口设计也满足要求,但在大多数场景下,可以使用更适合的设计,比如游标式(下文会介绍)。
常见问题
数据缺失:获取后页时,前页数据有删除。此时,本应出现在后页的内容被“顶”到前页,而前页已经加载过了不会重新加载,后页又无此内容,从而无法被用户看到。
数据重复:获取后页时,前页数据有插入。此时,原本在前页的内容被“压”到后页,导致前后也都有此数据,在用户端就是重复数据。
性能问题:较大页码的数据获取时性能会下降,计算总数也会带来额外的开销。
解决方案:
- 游标式分页参数设计:
- 客户端记录当前分页的最后一条数据的 ID(curcor)
- 请求下一页的时候,从这个 ID 开始获取一页大小(pageSize)的内容
优点:
- 能够避免数据重复/遗漏
- 无需计算offset,性能更稳定
缺点:
- 只适用于按照时间追加的方式等简单排序
- 无法跳到指定页,适合流式分页
- 一次性下发或缓存所有ID
- 请求第 1 页数据之前/时先缓存所有 ID 列表
- 请求第 2,3,…n 页数据时,只需传入单页相关的 ID 列表参数
优点:
- 可将排序由数据库移到应用容器,同时仅取ID一列,降低DB压力。
- 无需重复计算总数,性能更优更稳定。
缺点:
- 仅适用于 id 列表不会很大(数百条数据)的业务场景
- 限定数据生成时间
分页参数中再额外多一个timestamp参数,第一页请求时timestamp由后端生成并传给前端,前端在后页查询时将此值再次传回给后端,后端在查询条件中只用此值限定数据插入时间。
此方法可以解决数据重复,但无法解决数据缺失,因此适用于只增不删或极少删的场景。
常见性能问题优化方法
- SQL查两次,先查出所需页的ID,再用IN查询单页数据。
- 对热门数据缓存,如前n页。
- 在页数很靠后时,MySQL的limit会有比较大的性能问题,可以按倒数第n页的思路将排序方式反转查询。
另一种分页参数设计
在使用数据库做分页查询时,常见的方式通过行号rownum(SQL Server 、Oracle)或偏移offset(MySQL、SQL Server)来实现,因此有时候会将接口的参数设计成rowStart和rowCount。
rowStart,起始行索引,从0开始,rowStart = pageIndex * pageSize
rowCount,单页行数,即pageSize
但这种行方式设计要是需要转为page参数,却容易出现不兼容,比如rowStart=1,rowCount=5,此时上述的转换关系不成立。之前在做接口切换时遇到过一次,为做到兼容,使用了如下的转换算法,本质是找出可以覆盖到rowStart到rowEnd(rowStart+rowCount)的最小pageIndex和pageSize,然后从结果中取出最终需要的subList即可。
/// 分页参数换算
/// </summary>
/// <param name="rowIndexStart">起始行索引,从0开始,包含</param>
/// <param name="rowCount">单页行数</param>
/// <param name="pageIndex">页码索引,从0开始</param>
/// <param name="pageSize">单页行数</param>
/// <param name="resultIndexStart">最终结果起始索引,从0开始,包含</param>
private static void Row2Page(int rowIndexStart, int rowCount, out int pageIndex, out int pageSize, out int resultIndexStart)
{
if (rowIndexStart < 0 || rowCount <= 0)
{
pageIndex = 0;
pageSize = 10;
resultIndexStart = 0;
return;
}
pageSize = rowCount;
while (rowIndexStart % pageSize + rowCount > pageSize)
{
pageSize++;
}
pageIndex = rowIndexStart / pageSize;
resultIndexStart = rowIndexStart - pageIndex * pageSize;
}
网友评论