分页接口设计

两种分页方式

传统的分页方式页最典型的特点是页面上有一连串的页码,和电梯按钮相似,因此页常被称之为电梯式分页。

电梯式特点:

  • 通过页码进行分页
  • 通过点击上/下页按钮可实现页面切换
  • 通过点击页码可实现页面切换
  • 可直接跳转至指定页面
  • 多用于 PC 端,适合需要查找特定内容的页面
  • 需要计算总数or总页数(搜索引擎等场景也可以无需计算,相应的跳转按钮会有所限制)

电梯式分页适用于传统的页面布局,而在移动端页面上更流行的是瀑布流布局方式,相应的分页方式称为流式分页,有时也称为无限下拉式分页。

流式分页特点:

  • 通过滚动/上拉/点击等方式加载新一页
  • 无页码
  • 无上/下页按钮
  • 不可跳转至指定页面
  • pc端和移动端均有使用,适合UGC、视觉内容以及推荐系统等“浏览型”页面
  • 无需计算总数or总页数

对于用电梯式分页方式的接口,有页码、页大小以及结果总数等参数,页码需要明确是从1开始还是0开始,一般使用pageNumber表示从1开始,使用pageIndex表示页索引从0开始,pageSize和limint均可表示单页数据量。totalCount和totalPage可以分别表示总数据条数和总页数。

  • page(pageNumber、pageIndex)
  • pageSize/limit
  • total(totalCount、totalPage)

当然,对于流式分页,上述的接口设计也满足要求,但在大多数场景下,可以使用更适合的设计,比如游标式(下文会介绍)。

常见问题

数据缺失:获取后页时,前页数据有删除。此时,本应出现在后页的内容被“顶”到前页,而前页已经加载过了不会重新加载,后页又无此内容,从而无法被用户看到。

数据重复:获取后页时,前页数据有插入。此时,原本在前页的内容被“压”到后页,导致前后也都有此数据,在用户端就是重复数据。

性能问题:较大页码的数据获取时性能会下降,计算总数也会带来额外的开销。

解决方案:

  1. 游标式分页参数设计:
  • 客户端记录当前分页的最后一条数据的 ID(curcor)
  • 请求下一页的时候,从这个 ID 开始获取一页大小(pageSize)的内容

优点:

  • 能够避免数据重复/遗漏
  • 无需计算offset,性能更稳定

缺点:

  • 只适用于按照时间追加的方式等简单排序
  • 无法跳到指定页,适合流式分页
  1. 一次性下发或缓存所有ID
  • 请求第 1 页数据之前/时先缓存所有 ID 列表
  • 请求第 2,3,…n 页数据时,只需传入单页相关的 ID 列表参数

优点:

  • 可将排序由数据库移到应用容器,同时仅取ID一列,降低DB压力。
  • 无需重复计算总数,性能更优更稳定。

缺点:

  • 仅适用于 id 列表不会很大(数百条数据)的业务场景
  1. 限定数据生成时间

分页参数中再额外多一个timestamp参数,第一页请求时timestamp由后端生成并传给前端,前端在后页查询时将此值再次传回给后端,后端在查询条件中只用此值限定数据插入时间。

此方法可以解决数据重复,但无法解决数据缺失,因此适用于只增不删或极少删的场景。

常见性能问题优化方法

  1. SQL查两次,先查出所需页的ID,再用IN查询单页数据。
  2. 对热门数据缓存,如前n页。
  3. 在页数很靠后时,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即可。

/// 分页参数换算
/// 
/// 起始行索引,从0开始,包含
/// 单页行数
/// 页码索引,从0开始
/// 单页行数
/// 最终结果起始索引,从0开始,包含
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;
}

http://zhiheng.me/156

你可能感兴趣的:(分页接口设计)