如何优雅地实现分页查询

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 分页功能是很常见的功能,特别是当数据量越来越大的时候,分页查询是必不可少的。实现分页功能有很多种方式,如果使用的ORM框架是mybatis的话,有开源的分页插件可以使用,如:Mybatis-PageHelper。
img_e67674edd2cf04156bfce7db61c5da89.jpe

分页功能是很常见的功能,特别是当数据量越来越大的时候,分页查询是必不可少的。实现分页功能有很多种方式,如果使用的ORM框架是mybatis的话,有开源的分页插件可以使用,如:Mybatis-PageHelper。如果不使用分页插件,那么就需要手动分页了,由于不同的数据库实现分页的SQL语句并不一致,如Mysql使用的是limit关键字,而Oracle使用的是rownum,所以本文本文讲解的分页方案只适用于Mysql数据库。

基于limit的分页方案

首先讲讲分页操作必须满足的几个要求:一个是有序性,一个是不重复。有序性可以看成是不重复的前提条件,因为假如数据是无序的,那么就不能保证多个分页之间是不重复的。因此分页操作往往需要先对数据进行排序,然后再加上分页条件。我们讲的第一种方案是基于limit的分页方案,也是很多分页插件使用的分页方案。我们先来看看我们的测试数据。

先看一下表结构:

mysql> desc user;

+-------+------------+------+-----+---------+-------+

| Field | Type | Null | Key | Default | Extra |

+-------+------------+------+-----+---------+-------+

| id | bigint(20) | NO | PRI | NULL | |

| name | char(50) | NO | | NULL | |

+-------+------------+------+-----+---------+-------+

2 rows in set

可以看到我们的user表只有2列,分别是bigint型的id和char型的name。

接下来看下表数据:

mysql> select count(*) from user;

+----------+

| count(*) |

+----------+

| 45116 |

+----------+

1 row in set

mysql> select * from user order by id asc limit 10;

+----+--------+

| id | name |

+----+--------+

| 0 | user_0 |

| 1 | user_1 |

| 2 | user_2 |

| 3 | user_3 |

| 4 | user_4 |

| 5 | user_5 |

| 6 | user_6 |

| 7 | user_7 |

| 8 | user_8 |

| 9 | user_9 |

+----+--------+

10 rows in set

可以看到数据总行数大概45000条。

基于limit实现分页是比较简单的:

mysql> select * from user order by id asc limit 10000,10;

+-------+------------+

| id | name |

+-------+------------+

| 10000 | user_10000 |

| 10001 | user_10001 |

| 10002 | user_10002 |

| 10003 | user_10003 |

| 10004 | user_10004 |

| 10005 | user_10005 |

| 10006 | user_10006 |

| 10007 | user_10007 |

| 10008 | user_10008 |

| 10009 | user_10009 |

+-------+------------+

10 rows in set

其中,limit后面的第一个参数表示下标,也就是从第10000行记录开始取,第二个参数表示总共取10行记录。

使用limit实现分页功能使用起来非常简单,但是有没有什么问题呢?

我们先来回顾一下前面说的分页需要满足的2个要素:有序性和不重复。上述的语句我们已经使用了order by 进行排序,所以是可以满足有序性的,但满足了不重复了吗?假设在查询当前页跟下一页之间插入了一条记录,且该数据的id小于当前页记录中最大的id,会怎么样呢?我们测试一下就知道了:

mysql> select * from user order by id asc limit 10000,10;

+-------+------------+

| id | name |

+-------+------------+

| 10000 | user_10000 |

| 10001 | user_10001 |

| 10002 | user_10002 |

| 10003 | user_10003 |

| 10004 | user_10004 |

| 10005 | user_10005 |

| 10006 | user_10006 |

| 10007 | user_10007 |

| 10008 | user_10008 |

| 10009 | user_10009 |

+-------+------------+

10 rows in set

mysql> insert into user(id,name) values(-1,'user_-1');

Query OK, 1 row affected

mysql> select * from user order by id asc limit 10010,10;

+-------+------------+

| id | name |

+-------+------------+

| 10009 | user_10009 |

| 10010 | user_10010 |

| 10011 | user_10011 |

| 10012 | user_10012 |

| 10013 | user_10013 |

| 10014 | user_10014 |

| 10015 | user_10015 |

| 10016 | user_10016 |

| 10017 | user_10017 |

| 10018 | user_10018 |

+-------+------------+

10 rows in set

可以看到,当我们在相邻的2页查询之间插入一条记录的时候,后面一页跟前面一页有记录重复了(id为10009的记录在相邻2页中都出现了)。原因在于插入一条记录之后,分页结构已经改变了,所以才会出现重复数据。

因此,使用limit进行分页似乎不是很优雅啊,接下来将介绍另外一种分页的写法。

基于limit与比较的分页方案

另外一种分页的写法可以这样考虑,比如我们要取的是从第10000行开始的10行记录,那么我们可以先把大于或等于10000行的数据查出来并排序,然后再取出前10行记录,这样也可以完成分页。接下来看具体的SQL语句:

mysql> select * from user where id >=10000 order by id asc limit 10;

+-------+------------+

| id | name |

+-------+------------+

| 10000 | user_10000 |

| 10001 | user_10001 |

| 10002 | user_10002 |

| 10003 | user_10003 |

| 10004 | user_10004 |

| 10005 | user_10005 |

| 10006 | user_10006 |

| 10007 | user_10007 |

| 10008 | user_10008 |

| 10009 | user_10009 |

+-------+------------+

10 rows in set

那么这种写法可以防止上面出现的问题吗?我们做个试验就知道了。

mysql> select * from user where id >=10000 order by id asc limit 10;

+-------+------------+

| id | name |

+-------+------------+

| 10000 | user_10000 |

| 10001 | user_10001 |

| 10002 | user_10002 |

| 10003 | user_10003 |

| 10004 | user_10004 |

| 10005 | user_10005 |

| 10006 | user_10006 |

| 10007 | user_10007 |

| 10008 | user_10008 |

| 10009 | user_10009 |

+-------+------------+

10 rows in set

mysql> insert into user(id,name) values(-4,'user_-4');

Query OK, 1 row affected

mysql> select * from user where id >=10010

order by id asc limit 10;

+-------+------------+

| id | name |

+-------+------------+

| 10010 | user_10010 |

| 10011 | user_10011 |

| 10012 | user_10012 |

| 10013 | user_10013 |

| 10014 | user_10014 |

| 10015 | user_10015 |

| 10016 | user_10016 |

| 10017 | user_10017 |

| 10018 | user_10018 |

| 10019 | user_10019 |

+-------+------------+

10 rows in set

可以看到,当在相邻的两页查询之间插入数据时,分页查询结果不会出现重复。其实也很好理解,因为虽然插入记录后,分页的结构变了,但是由于我们现在的分页查询是从固定的id开始查的,所以插入新的数据对后面的分页结果没有影响。

当然,这种分页查询也是有限制的。其只适用于用来排序的列具有唯一性的情况,在上述例子中,id列是主键,所以具有唯一性,故可以使用这种方式分页。如果用来排序的列不具有唯一性,比如说是时间戳,那么这种分页方式也可能出现重复,大家可以想想是为什么。

如果你现在在JAVA这条路上挣扎,也想在IT行业拿高薪,可以参加我们的训练营课程,选择最适合自己的课程学习,技术大牛亲授,8个月后,进入名企拿高薪。我们的课程内容有:Java工程化、高性能及分布式、高性能、高架构、性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点。如果你想拿高薪的,想学习的,想就业前景好的,想跟别人竞争能取得优势的,想进阿里面试但担心面试不过的,你都可以来,QQ群号为:835638062

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
18天前
分页实现
分页实现
11 0
|
3月前
|
SQL Oracle 关系型数据库
3.分页
3.分页
|
JSON API 数据库
分页
1. program中注册`builder.Services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();` 2. 控制器中增加依赖
|
小程序 API 数据库
小程序中的分页查询
小程序中的分页查询
小程序中的分页查询
|
SQL Java 数据库连接
分页的实现
分页的实现
|
数据采集 算法 前端开发
查询分页不只有 limit,这四种分页方法值得掌握
查询分页不只有 limit,这四种分页方法值得掌握
201 0
查询分页不只有 limit,这四种分页方法值得掌握
分页查询(limit)
limit(可以接受一个参数或者两个参数) 第一个参数 指定第一个返回记录行的偏移量(初始偏移量是0不是1) 第二个参数 指定返回记录行的最大数目 例: //检索记录行6-15 select * from table limit 5...
1671 0