分页方式
|
优点
|
缺点
|
使用场景
|
交互方式
|
页码分页
|
用户能够明确知道总页数,清晰明了,实现简单
|
每页都需要去重新发起请求,即使该页已经访问过
|
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如下:
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…