常见分页形式
在列表页实现分页展示逻辑是十分常见的需求,但针对不同的场景和产品需求,分页功能有不同的表现形式。常见的有如下几种。
1、滚动翻页
下滑自动下一页,展示直到当前页的所有内容,不显示总页数、总条数,不支持上一页、跳页,不支持page size设置
2、上/下一页
只支持简单的上/下一页的操作,不显示总页数、总条数,不支持跳页,不支持page size设置
3、跳页(最为常见)
支持上/下一页、跳页,page size固定,不显示总页数
4、跳页、显示总页数
支持上/下一页、跳页,page size可设置,显示总页数
分页设计的两大难点问题
传统分页的话,一般只考虑传页数(page)和每页数据条数(limit)这两个参数给后端,为了方便后面描述,我们给这个传参方式起个名字叫传统分页。
传统分页存在两个突出的问题:
- 深分页:翻页过深导致拉取数据量过大,耗费大量IO和内存,极端情况可能导致OOM,效率低下
- 数据重复/丢失:数据变动导致分页数据重复/丢失
深分页
深分页问题在分页设计中较为常见,大家在设计时一般都会考虑到这个问题。该问题在分库分表或者存在数据分区的场景下更为严重。
面对不同的需求场景,解决深分页问题的方法也有所不同。下面以常见的利用MySQL实现分页的方案做一个简单的分析。
MySQL实现分页
方式1:limit + offset
select * from {
{table}} order by id limit m, n;
很简单,该语句的意思就是查询m+n条记录,去掉前m条,返回后n条。
无疑该查询能够实现分页,但m越大,查询性能就越低,因为MySQL需要扫描全部m+n条记录(在存在数据分区的情况下,需要扫描(m+n)*分区数条记录)。
适用场景:支持跳页,但要求数据量较小,深分页带来的存储和性能压力可以接受
方式2:max_id + limit
select * from {
{table}} where id > {
{max_id}} order by id limit n;
该查询同样会返回后n条记录,却无需像方式1扫描前m条记录,但必须在每次查询时拿到上一次查询(上一页)的最大id。
该查询的问题也在于我们不一定能拿到这个id,比如当前在第3页,需要查询第5页的数据,就不行了。
适用场景:不支持跳页,只能上下翻页
方式3:max_id + limit + offset
为了避免方式2不能实现的跳页查询,就需要结合方式1。
性能需要,m得尽量小,比如当前在第3页,需要查询第5页,每页10条数据,而当前第3页的最大id为max_id,则:
select * from table where id > {
{max_id}} order by id limit 10, 10;
该查询方式实际是方式1和方式2的结合,它只能部分地解决方式2的问题。
如果当前在第2页,要查第1000页(跳转尾页),查询的数据量仍然很大(需要查询10*998条数据,然后过滤出后10条)。
适用场景:支持跳页,相对方式1可以适用于较大的数据量,但极端情况(从第一页跳页)下会退化为方式1。
方式4:subquery + inner join
select * from table as a inner join (select id from table order by id limit m, n) as b on a.id = b.id order by a.id;
该查询同方式1一样,m的值可能很大,但由于内部的子查询只扫描了id字段,而非全表,所以性能要强于方式1(可以命中覆盖索引设置主键索引),并且能够解决方式2和方式3不能解决的问题。
适用场景:支持跳页,相对方式1和方式3可以适用于更大的数据量