相关文章推荐
另类的人字拖  ·  Stuck in "Preparing ...·  5 月前    · 
冲动的野马  ·  C# ...·  1 年前    · 
分页方式 优点 缺点 使用场景 交互方式
页码分页 用户能够明确知道总页数,清晰明了,实现简单 每页都需要去重新发起请求,即使该页已经访问过 web端比较常用的一种分页方式 翻页加载页面;首页尾页导航;
滑动分页 体验比较好,加载过数据后,查看之前的数据,不需要再次加载,纵享丝滑 需要缓存前面页的数据,数据量较多时,占用存储空间。 移动端交互比较常用的一种分页方式 下滑加载数据;上拉重载页面;

2. 数据重复问题分析

2.1 问题简述

电商系统中,用户查询已收货包裹数据时,向上滑动,新加载的第二页数据与第一页数据有重复内容。

2.2 查询sql分析

包裹表(quotation_package_info)

简要表结构如下:

字段 类型 简述
id int 主键,自增id
package_no varchar(32) 包裹单号
recycler_id int 商家id
status int 包裹状态,1待收货,2已收货
create_dt datetime 创建时间
update_dt datetime 最后修改时间
express_no varchar(32) 快递单号
deliver_dt datetime 发货时间
receive_dt datetime 收货时间

查询sql如下:

-- 查询某商家的所有已收货包裹,根据id倒序排列
SELECT p.id,
       p.package_no,
       p.recycler_id,
       p.express_no,
       p.status,
       p.deliver_dt,
       p.receive_dt,
       p.create_by,
       p.create_dt,
       p.update_by,
       p.update_dt
FROM quotation_package_info p
where p.recycler_id = '商家id'
  and p.status = '2'
order by p.id desc limit startIndex:pageSize;

2.3 问题分析与抽取

  • 已收货包裹默认按照数据表中主键进行降序排序。
  • 第一页的第一条数据为当前该商户已收货包裹,id最大的记录。
  • 前端拉取到第一页数据后,该商户此时有新收货记录变动,且该条记录的id大于第一页第一条数据对应的id。
  • 拉取第二页数据时,之前第一页的最后一条记录,被排序到第二页的第一条,出现数据重复。
  • 与上述场景类似的分页查询都会有这种问题,抽取该问题的共性条件如下:

  • 数据集根据某一个或几个字段进行排序。
  • 根据排序条件无法确定一个稳定的数据集,新插入的数据,按照排序字段进行排序,可能插在整个数据集的头部或者中间。
  • 查询某一页数据时,如果该页之前的数据集存在新插入的数据,该页一定会查询到重复的数据。
  • 思考:该问题与分页展示方式无关,不管是“滑动分页”还是”页码分页“都存在此问题,但是”页码分页“第二页会替换掉第一页的展示内容,不易被用户察觉,而”滑动分页“的数据是追加展示的,用户易察觉到数据重复的问题。

    3. 解决思路

    3.1 思路一:查询完整的数据集

    不进行分页,一次拉取所有的数据,原始数据集无重复,就不会拉取到重复的数据。

  • 规避掉了分页存在的问题。
  • 数据量比较少的时候,比分页查询的效率更高。
  • 数据规模比较大时,查询慢、传输慢、网络超时。
  • 数据集较多时,前端渲染慢,用户等待加载数据时间长,易感知。
  • 数据规模比较小的情况。

    3.2 思路二:固定数据集范围

    3.2.1 固定分段数据集

  • 通过增加数据开始条件、结束条件,取一段固定的数据集。
  • 或者调整排序条件,固定一侧数据不会变更。
  • 前端不需要进行处理,数据集是固定的,只关注分页页码、每页展示条数即可

    适用场景有限,需要结合业务场景进行分析,必须能够固定住数据集两侧,或者至少能固定住数据集开头

    能够固定住数据集的场景

    3.2.1.1 案例分析

    阿里云日志服务数据查询

  • 第一页数据
  • from: 1612257085 to: 1612257985 Page: 1 Size: 20 "message": "successful", "data": { "count": 20, "logs": [ {},{},{} "code": "200", "success": true
  • 第二页数据
  • from: 1612257085 to: 1612257985 Page: 2 Size: 20 "message": "successful", "data": { "count": 20, "logs": [ {},{},{} "code": "200", "success": true
  • 日志跟时间关联的,新增数据的时间戳更大,采用日志生成时间戳倒序排列的话,如果不限制范围,分页数据会很快被覆盖掉。
  • 上述接口通过传参 from、to 限制住了要查询的日志的开始时间戳、结束时间戳,固定了数据集的范围,解决了分页的问题。
  • 3.2.2 固定某一页的数据集

  • 第一页数据是固定的,前端保存第一页最后一条数据对应的排序条件的值
  • 查询后面第n页时,将第n-1页的最后一条记录对应的排序条件的值作为过滤参数
  • 限制了每一页数据的开始条件,后台服务、前端界面代码修改都比较简单
  • 如果排序条件为主键或者索引值,查询效率会更高。
  • 只适用于单条件排序,且排序条件具有唯一性。
  • 只适用于滑动分页,不支持页码分页,不根据页码定位数据。
  • 未下拉刷新之前,数据集中新插入数据,无法展示。
  • 只适用于滑动分页,单条件排序,排序条件的值在数据集中唯一。
  • 能够容忍未下拉刷新前,数据集不展示新插入的数据。
  • 3.2.2.1 案例分析

    淘宝收藏夹

  • 第一页数据
  • "startRow": 0, "startTime": 0, "pageSize": 30, "pageNum": 0, "hasMore": "true", "api":"mtop.taobao.mercury.platform.collections.get", "data":{ "favList":[ {},{},{}, "collectTime":"2020-03-27 21:48:03", "shopUrl":"//shop.m.taobao.com/shop/shopIndex.htm?seller_id=92688455", "pageInfo":{ "hasMore":"true", "nextStartTime":"1585316883404", "pageSize":"30", "preloadPage":"true", "startRow":"0", "totalCount":"0" "ret":[ "SUCCESS::调用成功" "v":"5.1"
  • 第二页数据 手机端拉取第一页数据后,pc端再进行一条收藏,第二页无重复数据
  • "startRow":0, "startTime":"1585316883404", "pageSize":30, "pageNum":1, "hasMore":"true", "api":"mtop.taobao.mercury.platform.collections.get", "data":{ "favList":[ "collectTime":"2019-09-13 09:15:03", "shopName":"佐卡曼旗舰店", "shopUrl":"//shop.m.taobao.com/shop/shopIndex.htm?seller_id=2973681982" "pageInfo":{ "hasMore":"true", "nextStartTime":"1568337303676", "pageSize":"30", "preloadPage":"false", "startRow":"0", "totalCount":"0" "ret":[ "SUCCESS::调用成功" "v":"5.1"
  • 淘宝收藏夹是存在数据集不固定的情况的,目前是按照收藏顺序倒序排列的,如果此时手机端拉取了第一页的收藏记录,此时pc端新加入收藏,新收藏的商品会被插入到数据集的最前面。
  • 淘宝分页查询收藏夹接口,第2页会传入第一页最后一条数据的收藏时间,第3页会传入第2页最后一条数据的收藏时间,保证拉取到的数据不会重复。
  • 可以倒推出查询收藏夹接口是按照收藏时间倒序排列,且通过某种机制保证了收藏时间不会重复(服务器时间无问题的话,精度到毫秒级,收藏时间完全重复的概率本身就比较低)。
  • 3.3 思路三:前端过滤重复数据集

  • 每拉取到一页数据,前端将所有的唯一id缓存
  • 比对当前页给出的数据,如果已经存在,不进行渲染
  • 判断当前页过滤掉的条数,比对阈值,如果超过了阈值,再拉取一页,避免用户感知到。
  • 接口无需考虑具体的业务场景,直接进行分类即可,适用场景较多

  • 前端需要缓存已拉取到的所有的数据,用于判断去重,占用内存
  • 缓存数据较多时,去重判断的次数也递增,渲染效率较低
  • 未下拉刷新之前,数据集中新插入数据,无法展示。
  • 一般用于滑动分页
  • 数据量较少(缓存小,速度快),或能够推断出调用分页次数较少的场景。
  • 能够容忍未下拉刷新前,数据集不展示新插入的数据。
  • 3.3.1 案例分析

    京东收藏夹

  • 第一页数据
  • pageSize=10 "cp": "1", "data": [ {},{},{},{},{}, "commCategory": "670;677;680", "commColor": "「D42666频」", "commId": "8391337", "commSize": "单条【8G】", "commStatus": "1", "commTitle": "金士顿(Kingston)8GBDDR42666笔记本内存条骇客神条Impact系列", "favPeopleNum": "0", "favPrice": "469.000000", "favTime": "1547304069000", "venderId": "1000000192" "errMsg": "", "iRet": "0", "totalNum": "79", "totalPage": "0"
  • 第二页数据 手机端拉取第一页数据后,pc端再进行一条收藏,第二页有重复数据
  • pageSize=10 "cp": "2", "data": [ "commCategory": "670;677;680", "commColor": "「D42666频」", "commId": "8391337", "commSize": "单条【8G】", "commStatus": "1", "commTitle": "金士顿(Kingston)8GBDDR42666笔记本内存条骇客神条Impact系列", "favPeopleNum": "0", "favPrice": "469.000000", "favTime": "1547304069000", "venderId": "1000000192" },{},{},{},{} "errMsg": "", "iRet": "0", "totalNum": "80", "totalPage": "0"
  • 京东收藏夹是存在数据集不固定的情况的,目前是按照收藏顺序倒序排列的,如果此时手机端拉取了第一页的收藏记录,此时pc端新加入收藏,新收藏的商品会被插入到数据集的最前面
  • 接口入参中,只传递了页码、每页条数信息
  • 手机端拉取到第一页数据后,pc端新收藏一个商品,此时手机端拉取第二页数据,可以看出第二页的第一条与第一页的最后一条是重复的,且 totalNum+1。
  • 接口数据有重复,客户端展示的数据是正常的,客户端进行了去重展示
  • 可以反推出,京东收藏夹是按照收藏时间倒序排列的,且客户端进行了去重处理。
  • 3.4 已收货包裹分页数据重复解决思路分析

  • 收货包裹中的数据量较大,需要分页,思路一不可行
  • 当前根据id排序,属于单条件排序,且id具有唯一性,思路二可行
  • SELECT p.id,
           p.package_no,
           p.recycler_id,
           p.express_no,
           p.status,
           p.deliver_dt,
           p.receive_dt,
           p.create_by,
           p.create_dt,
           p.update_by,
           p.update_dt
    FROM quotation_package_info p
    where p.recycler_id = '商家id'
      and p.status = '2'
      and p.id < :lastPageLastDataId
    order by p.id desc limit :pageSize;
    
  • 商家的已收货包裹,数据量不会特别大,客户端缓存数据量不多,而且一般用户滑动收货列表n次后,看不到记录,会采用精准搜索,思路三可行
  • 综上,该问题可采用思路二或者思路三解决。

    4. 总结

    分页数据重复问题,还是要结合具体业务场景进行分析,没有适用于所有场景的解决思路。
    采用滑动分页方式的接口,数据重复是比较容易被用户感知到的,滑动分页是移动端普遍采用的一种分页方式,因此在设计移动端接口,以及在修改对应的sql语句时,要结合业务场景来进行具体分析,规避这种问题。

    附:抓取移动端数据包

    抓包工具:chrome debug 、wireshark、charles、fildder chrome debug: 自适应调整,抓取移动端数据包 pc端微信:微信小程序抓取数据包 charles抓包教程:juejin.cn/post/684490… https抓包原理:juejin.cn/post/684490…

    分类:
    后端
    标签: