为什么要分页?

发布于 2022-07-13  2.07k 次阅读


我,一名新入职的工程师;

我,缺乏高并发实战经验;

我,后端回参上千条数据;

我,循环访问数据库一次查一条;

我,从不分页,从不批量处理;

我被称为性能杀手,服务器终结者。

 

秋招时,我能和面试官大谈高并发,高可用,高扩展;答辩时,我能和老师探讨多线程,分布式锁,性能优化...

面试官和老师眼中的我:

查看源图像

实际上的我:

动图

 

实际工作中,为什么要分页?为什么不应该在循环中查询数据库?怎样在工作中去考虑性能问题?相信本文能带给你一些思考。


一、为什么要分页?

分页功能在网页中是非常常见的一个功能,其作用也就是将数据分割成多个页面来进行显示。

  • 使用场景: 当取到的数据量达到一定的时候,就需要使用分页来进行数据分割。

当我们不使用分页功能的时候,会面临许多的问题:

  • 客户端的问题: 如果数据量太多,都显示在同一个页面的话,会因为页面太长严重影响到用户的体验,也不便于操作,也会出现加载太慢的问题
  • 服务端的问题: 如果数据量太多,可能会造成内存溢出(OOM),而且一次请求携带的数据太多,对服务器的性能也是一个考验

 

以C/S架构为例,要实现“展示所有的商品信息”功能,实现流程是:用户点击功能按钮,前端发起请求,后端接收请求去查对应库表,返回响应给前端,前端将数据渲染到App页面上。

假使商品信息表中有上百万条记录,如果不做分页处理,后端直接从表中获取百万条记录

服务器压力过大

首先服务器的内存可能就会溢出,即OOM。

假设公司老板财大气粗,服务器内存特别大,JVM的内存参数设置的也很大,内存没有爆掉,那么就来到了“返回响应给前端”这一步。

网络开销过大

我们都知道,网络传输的数据量越多,时延也会越高。

百万条记录在网络中的传输,究竟要多久呢?

以虚拟机为64位的机器为例,假设单个商品信息对象有8个字段且都是基本数据类型,对象头占用的内存是8(运行时数据)+4(类型指针)=12Byte,实例数据是(8个字段)8 * 4 = 32Byte,由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍,所以按照一个商品信息对象 48Byte的大小进行计算,百万条数据的大小为:

48 * 100W = 45.77MB

传输时间过长

接下来,计算45.77MB在网络速度为100Mb/S的情况下的传输时长为:

45.77 MB* 8bit / 100Mb = 3.66s

实际消耗肯定比3.66s更长,因为有损耗

注意,这仅仅是数据在网络中传输需要的时长,在此之前,客户端和服务端还需要经历TCP三次握手建立连接等等,在前端接收到数据之后,还要对数据进行解析、格式处理等等...

即使假设系统优化做的非常好,客户端的网络、硬件配置也特别高,整个流程仅花费了网络传输的时长3.66s,也无法接受。

用户体验太差

根据1/3/5秒原则,在1s以内得到响应,用户会觉得系统响应很快,体验非常好;1-3秒得到响应,用户可以接收,体验还不错;3-5秒才响应,用户就感觉慢了,体验有点糟糕;一旦响应超过5秒,用户就会认为是个失败的体验,选择离开或重新发起请求。

所以,为什么要一次性返回百万级别的数据呢,如此庞大的网络开销,如此缓慢的响应时间,如此不可接受的用户体验...槽点太多以至于无法吐槽。

那么,我们必须思考一下,用户需要一次性查看这么多条数据吗?

显然不需要。用户想要查看所有的商品信息,但他不可能一目十行,而且手机显示屏也装不下百万条数据,所以他一定是分批次去查看数据。就好像我们看小说一样,一本书300W字,不可能在同一页面展示,所以我们一定是这页看完了再去看下一页。

二、有哪些分页方式?

分页的时间节点

对于用户来说,他并不关心分页怎么实现,但对程序员来说,分页的实现有多种选择。

以 “展示所有的商品信息” 为例,从请求发起到返回数据的整个过程如下图所示:

从上图观察得知,我们有三个节点可以进行分页处理,分别是:

  • 数据库分页
  • 后端逻辑分页
  • 前端逻辑分页

其中,数据库分页为物理分页,后端前端分页为逻辑分页

1.真分页(物理分页):

  • 实现原理:SELECT * FROM xxx [WHERE...] LIMIT #{param1}, #{param2}
    第一个参数是开始数据的索引位置
    第二个参数是要查询多少条数据
  • 优点: 不会造成内存溢出
  • 缺点: 翻页的速度比较慢

2.假分页(逻辑分页):

  • 实现原理: 一次性将所有的数据查询出来放在内存之中,每次需要查询的时候就直接从内存之中去取出相应索引区间的数据
  • 优点: 分页的速度比较快
  • 缺点: 可能造成内存溢出

分页时间节点的选择

选择的标准是速度,显而易见,数据库,服务器和客户端之间是网络,如果网络传递的数据量越少,则客户端获得响应的速度越快。而且一般来说,数据库和服务器的处理能力一般比客户端要强很多。从这两点来看,客户端分页的方案是最不可取的。

其次就剩下了在服务器端分页和在数据库端分页两种方式了,如果选择在服务器端分页的话,大部分的被过滤掉的数据还是被传输到了服务器端,与其这样还不如直接在数据库端进行分页。

前端要做的就是尽快接受数据并最快地展示给用户,对于数据不多的场景用前端实现也无妨,然而若考虑到以后会有成千上万条数据应用的场景,显然后端去处理分页更合适些。
每次点击下一页,前端只需发送分页数信息请求后端数据,假设一页显示十条数据,每次点击只需请求这十条数据的信息返回给前端来更快地进行交互。然而若是由前端来进行分页操作,那就得把成千上万条数据全部先拉下来,再进行操作,先不说操作这么多数据拉低的性能,光是先拉下来就得费很长时间了,所以对于数据量大的操作,一般都采用后端分页的操作更合适。
首先要了解为什么要分页。分页主要是为了避免一次性从数据库获取大量数据。其次才是为了展示效果。

因此:数据库端分页 > 后端分页 > 前端分页

 三、各个分页方式的实现

数据库分页(以MySQL为例)

1.LIMIT用法

LIMIT出现在查询语句的最后,可以使用一个参数或两个参数来限制取出的数据。其中第一个参数代表偏移量:offset(可选参数),第二个参数代表取出的数据条数:rows。

  • 单参数用法

当指定一个参数时,默认省略了偏移量,即偏移量为0,从第一行数据开始取,一共取rows条。

/* 查询前五条数据 */

SELECT * FROM Student LIMIT 5;

  • 双参数用法

当指定两个参数时,需要注意偏移量的取值是从0开始的,此时可以有两种写法:

/* 查询第1-10条数据 */

SELECT * FROM Student LIMIT 0,10;


/* 查询第11-20条数据 */

SELECT * FROM Student LIMIT 10 OFFSET 10;

2. 分页公式

在进行分页之前,我们需要先根据数据总量来得出总页数,这需要用到COUNT函数和向上取整函数CEIL,此处略。

后端逻辑分页 && 前端逻辑分页

对于要显示的数据一次性从数据库全部查出,一直存放在服务端或客户端,在前端进行分页或由服务端控制分页。将根据当前所在页来计算应该显示的数据所在下标,用循环取出目标数据。只有当会话断开或页面关闭,相应的资源才会被释放。


总结

无论是本文提出的“为什么要分页”,还是“为什么要异步调用”等,在业务开发中,所有为什么的答案最终都指向共同的地方--->用户需求。这个用户的定义很广泛,可以是客户,可以是产品经理,可以是你的同事,甚至可以是你自己。

要记住,分页,多线程,分布式等等技术服务于业务,技术是用来解决问题的。

那么,一定要用分页吗?如果要用的话,选择哪种分页方式,物理分页还是逻辑分页?

我想,对于这个问题,应该从以下几点进行分析:

  1. 服务端执行本次查询的耗费(查询结果占用的内存,与数据库交互的耗时等)
  2. 客户端获取服务器响应的耗费(网络传输的大小,耗时,数据处理的耗时,页面渲染的耗时等)
  3. 如果使用,对当前的系统复杂度是否有影响
  4. 用户体验是否良好

我们应当将这四点(或者更多)作为实现功能的准则,才能编写出良好的系统。先思考,再写代码,是一门艺术。

 

参考资料:

(40条消息) 前端分页和后端分页的思考_迟来的阿牛的博客-CSDN博客_后端分页前端实现

几百万数据放入内存不会把系统撑爆吗? - 知乎 (zhihu.com)

使用MySQL实现分页查询 - 腾讯云开发者社区-腾讯云 (tencent.com)

Java Web -【分页功能】详解 - 简书 (jianshu.com)

分页技术及其实现 - 春风博客 - BlogJava

聊聊TCP连接耗时的那些事儿 - 腾讯云开发者社区-腾讯云 (tencent.com)

500MB的数据在网络速度为100Mb/s的情况下需要传输多久_百度知道 (baidu.com)

性能指标之响应时间 - 腾讯云开发者社区-腾讯云 (tencent.com)


她喜欢所以就做咯