1. 常见的分页方式
1.1 页码分页
页码分页展示区域是固定的,通过调整页码,来变更展示区域的内容,第n页与第n-1页是替代(replace)关系,可查看的数据只有1页。
1.2 滑动分页
滑动分页展示区域不是固定的,通过上页滑动,不断追加新的内容,第n页与第n-1页是追加(append)关系,滑动的越多,可查看的数据就越多。
1.3 比较
分页方式 | 优点 | 缺点 | 使用场景 | 交互方式 |
---|---|---|---|---|
页码分页 | 用户能够明确知道总页数,清晰明了,实现简单 | 每页都需要去重新发起请求,即使该页已经访问过 | 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 案例分析
京东收藏夹
- 第一页数据
入参:
cp=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端再进行一条收藏,第二页有重复数据
入参:
cp=2
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…