游标翻页

深翻页问题

普通翻页前端一般会有个分页条。能够指定一页的条数,以及任意选择查看第几页

对应的参数就是pageNopageSize

image (3).png

假设前端想要查看第11页的内容,传的值pageNo=11,pageSize=10

对于数据库的查询语句就是。

1
select * from table limit 100,10

其中100代表需要跳过的条数,10代表跳过指定条数后,往后需要再取的条数。

image (2).png

对应图片就是这样的一个效果,需要在数据库的位置先读出100条,然后丢弃。丢弃完100条后,再继续取10条选用

如果我们翻页到了很深的地方,比如读到了第1000页,对应的sql语句就是

1
select * from table limit 10000,10

需要先查询10000条进行丢弃,再取那么个10条选用。这个效率也太低了,

我们经常需要定时任务全量去跑一张表的数据,普通翻页去跑的话,到后面数据量大的时候,就会越跑越慢,这就是深翻页带来的问题。

有没有解决办法呢?有!

好好思考下,目前的问题在于每次翻页都需要花时间扫描一些不需要的记录,然后丢弃。那么是不是可以优化这个步骤呢?

以后不论第几页,我们都不需要跳过一些值。直接取limit 0,10。这样语句变成了

1
select * from table limit 0,10

取到的是1-10这些记录,取不到我们想要的101-110.

没关系,再加一个条件

1
select * from table where id>100 order by id limit 0,10

只要id这个字段有索引,就能直接定位到101这个字段,然后去10条记录。以后无论翻页到多大,通过索引直接定位到读取的位置,效率基本是一样的。这个id>100就是我们的游标,这就是游标翻页。

游标翻页简单介绍

游标翻页可以完美的解决深翻页问题,依赖的就是我们的游标,即cursor。针对mysql的游标翻页,我们需要通过cursor快速定位到指定记录,意味着游标必须添加索引。

前端之前传的pageNo字段改成了cursor字段。cursor是上一次查询结果的位置,作为下一次查询的游标,由后端返回,具体交互可看抹茶的前端后是如何协作的

我们来模拟一次前后端的翻页交互

随着翻页的持续,游标不断往翻页的方向推进。

所以游标翻页不适合跳页,只能不断的往下翻。更适合C端的列表场景

总结

游标翻页的优点

1.解决深翻页问题

2.解决频繁变动的列表翻页问题。

缺点

1.无法跳页,只能不断往下翻

游标翻页更适合c端场景,用户只能不断下滑翻页。

普通翻页更适合B端场景。用户能看见总页数,能随意跳页

游标翻页技术细节

我们的项目大量使用到了游标翻页,比如会话列表,消息列表,成员列表。其中数据库涉及了redis和mysql。并且封装了分页工具类,接下来就一起看看具体的实现细节。

mysql游标翻页

原生的游标翻页

image (7).png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
LambdaQueryChainWrapper<UserFriend> wrapper = lambdaQuery();
//游标字段(快速定位索引位置)
wrapper.lt(UserFriend::getId,cursorPageBaseReq.getCursor());
//游标方向
wrapper.orderByDesc(UserFriend::getId);
//额外查询条件
wrapper.eq(UserFriend::getUid,uid);
//游标翻页结果
Page<UserFriend> page = page(cursorPageBaseReq.plusPage(), wrapper);
//计算游标位置
String cursor = Optional.ofNullable(CollectionUtil.getLast(page.getRecords()))
.map(UserFriend::getId)
.map(String::valueOf)
.orElse(null);
//是否最后一页判断
Boolean isLast = page.getRecords().size() != cursorPageBaseReq.getPageSize();
return new CursorPageBaseResp<>(cursor, isLast, page.getRecords());

这个是一个完整的游标翻页的写法,也是我们消息列表的游标翻页的写法。

一个游标翻页查询最重要的,就是设定游标确定方向

以及查出结果后,要包装给前端的参数 当前游标位置是否最后一页

这样的查询基本是一个固定的套路,当你有其他的业务,比如会话列表也需要用到游标翻页,也是按照这个固定套路来进行查询的。

不知道你们觉得这块代码够不够简单,反正我觉得太臃肿了,再写一遍就吐了。

我们可以尝试把固定的游标模板抽出来,把可变的作为入参让业务传进来,形成一个工具类。

通过上面的分析,额外查询条件游标字段这个是可变的,其他都可以抽出来。

返回:

image (8).png

返回的也是可以直接给前端展示的实体类。