海量数据的分页怎么破?我的方案这样“改良”后,实在太完美!

大数据量limit分页优化 背景 在通常的业务场景中,分页一般是使用MySQL提供的 limit 来实现从数据库中获取指定偏移量的数据行。在数据量较小的场景中,并没有太大的问题,但当数据量膨胀到一定程度,获取靠后的分页数据时,性能下降就会特别的大。 原因在于MySQL需要根据偏移量扫描出所有符合的数据,然后再从中取分页记录数返回,其余数据全部丢弃。如 select * from t limit 100000, 10; MySQL需要扫描 100010 行数据并返回最后的10条。 解决方案 1.限制分页数量(减少... 阅读详情

一、背景

分页应该是极为常见的数据展现方式了,一般在数据集较大而无法在单个页面中呈现时会采用分页的方法。各种前端UI组件在实现上也都会支持分页的功能,而数据交互呈现所相应的后端系统、数据库都对数据查询的分页提供了良好的支持。以几个流行的数据库为例:

查询表 t_data 第 2 页的数据(假定每页 5 条)

  • MySQL 的做法:select * from t_data limit 5,5
  • PostGreSQL 的做法:select * from t_data limit 5 offset 5
  • MongoDB 的做法:
db.t_data.find().limit(5).skip(5);

尽管每种数据库的语法不尽相同,通过一些开发框架封装的接口,我们可以不需要熟悉这些差异。如 SpringData 提供的分页接口:

public interface PagingAndSortingRepository<T, ID extends Serializable>
  extends CrudRepository<T, ID> {

  Page<T> findAll(Pageable pageable);
}

这样看来,开发一个分页的查询功能是非常简单的。然而万事皆不可能尽全尽美,尽管上述的数据库、开发框架提供了基础的分页能力,在面对日益增长的海量数据时却难以应对,一个明显的问题就是查询性能低下!那么,面对千万级、亿级甚至更多的数据集时,分页功能该怎么实现?

下面,我以 MongoDB 作为背景来探讨几种不同的做法。

二、传统方案

就是最常规的方案,假设 我们需要对文章 articles 这个表(集合) 进行分页展示,一般前端会需要传递两个参数:

  • 页码(当前是第几页)
  • 页大小(每页展示的数据个数)

按照这个做法的查询方式,如下图所示:

image

因为是希望最后创建的文章显示在前面,这里使用了**_id 做降序排序**。其中红色部分语句的执行计划如下:

{
  "queryPlanner" : {
    "plannerVersion" : 1,
    "namespace" : "appdb.articles",
    "indexFilterSet" : false,
    "parsedQuery" : {
      "$and" : []
    },
    "winningPlan" : {
      "stage" : "SKIP",
      "skipAmount" : 19960,
      "inputStage" : {
        "stage" : "FETCH",
        "inputStage" : {
          "stage" : "IXSCAN",
          "keyPattern" : {
            "_id" : 1
          },
          "indexName" : "_id_",
          "isMultiKey" : false,
          "direction" : "backward",
          "indexBounds" : {
            "_id" : [ 
              "[MaxKey, MinKey]"
            ]
         ...
}

可以看到随着页码的增大,skip 跳过的条目也会随之变大,而这个操作是通过 cursor 的迭代器来实现的,对于cpu的消耗会比较明显。而当需要查询的数据达到千万级及以上时,会发现响应时间非常的长,可能会让你几乎无法接受!

或许,假如你的机器性能很差,在数十万、百万数据量时已经会出现瓶颈

三、改良做法

既然传统的分页方案会产生 skip 大量数据的问题,那么能否避免呢?答案是可以的。改良的做法为:

  1. 选取一个唯一有序的关键字段,比如 _id,作为翻页的排序字段;
  2. 每次翻页时以当前页的最后一条数据_id值作为起点,将此并入查询条件中。

如下图所示:

image

修改后的语句执行计划如下:

{
  "queryPlanner" : {
    "plannerVersion" : 1,
    "namespace" : "appdb.articles",
    "indexFilterSet" : false,
    "parsedQuery" : {
      "_id" : {
        "$lt" : ObjectId("5c38291bd4c0c68658ba98c7")
      }
    },
    "winningPlan" : {
      "stage" : "FETCH",
      "inputStage" : {
        "stage" : "IXSCAN",
        "keyPattern" : {
          "_id" : 1
        },
        "indexName" : "_id_",
        "isMultiKey" : false,
        "direction" : "backward",
        "indexBounds" : {
          "_id" : [ 
            "(ObjectId('5c38291bd4c0c68658ba98c7'), ObjectId('000000000000000000000000')]"
          ]
      ...
}

可以看到,改良后的查询操作直接避免了昂贵的 skip 阶段,索引命中及扫描范围也是非常合理的!

性能对比

为了对比这两种方案的性能差异,下面准备了一组测试数据。

测试方案

准备10W条数据,以每页20条的参数从前往后翻页,对比总体翻页的时间消耗

db.articles.remove({});
var count = 100000;

var items = [];
for(var i=1; i<=count; i++){

  var item = {
    "title" : "论年轻人思想建设的重要性-" + i,
    "author" : "王小兵-" + Math.round(Math.random() * 50),
    "type" : "杂文-" + Math.round(Math.random() * 10) ,
    "publishDate" : new Date(),
  } ;
  items.push(item);

  if(i%1000==0){
    db.test.insertMany(items);
    print("insert", i);

    items = [];
  }
}

传统翻页脚本

function turnPages(pageSize, pageTotal){

  print("pageSize:", pageSize, "pageTotal", pageTotal)

  var t1 = new Date();
  var dl = [];

  var currentPage = 0;
  //轮询翻页
  while(currentPage < pageTotal){

     var list = db.articles.find({}, {_id:1}).sort({_id: -1}).skip(currentPage*pageSize).limit(pageSize);
     dl = list.toArray();

     //没有更多记录
     if(dl.length == 0){
         break;
     }
     currentPage ++;
     //printjson(dl)
  }

  var t2 = new Date();

  var spendSeconds = Number((t2-t1)/1000).toFixed(2)
  print("turn pages: ", currentPage, "spend ", spendSeconds, ".")  

}

改良翻页脚本

function turnPageById(pageSize, pageTotal){

  print("pageSize:", pageSize, "pageTotal", pageTotal)

  var t1 = new Date();

  var dl = [];
  var currentId = 0;
  var currentPage = 0;

  while(currentPage ++ < pageTotal){

      //以上一页的ID值作为起始值
     var condition = currentId? {_id: {$lt: currentId}}: {};
     var list = db.articles.find(condition, {_id:1}).sort({_id: -1}).limit(pageSize);
     dl = list.toArray();

     //没有更多记录
     if(dl.length == 0){
         break;
     }

     //记录最后一条数据的ID
     currentId = dl[dl.length-1]._id;
  }

  var t2 = new Date();

  var spendSeconds = Number((t2-t1)/1000).toFixed(2)
  print("turn pages: ", currentPage, "spend ", spendSeconds, ".")    
}

以100、500、1000、3000页数的样本进行实测,结果如下:

image

可见,当页数越大(数据量越大)时,改良的翻页效果提升越明显!这种分页方案其实采用的就是时间轴(TImeLine)的模式,实际应用场景也非常的广,比如Twitter、微博、朋友圈动态都可采用这样的方式。而同时除了上述的数据库之外,HBase、ElastiSearch 在Range Query的实现上也支持这种模式。

四、完美的分页

时间轴(TimeLine)的模式通常是做成“加载更多”、上下翻页这样的形式,但无法自由的选择某个页码。那么为了实现页码分页,同时也避免传统方案带来的 skip 性能问题,我们可以采取一种折中的方案。

这里参考Google搜索结果页作为说明:

image

通常在数据量非常大的情况下,页码也会有很多,于是可以采用页码分组的方式。以一段页码作为一组,每一组内数据的翻页采用ID 偏移量 + 少量的 skip 操作实现

具体的操作如下图所示:

image

实现步骤

  1. 对页码进行分组(groupSize=8, pageSize=20),每组为8个页码;
  2. 提前查询 end_offset,同时获得本组页码数量:db.articles.find({ _id: { $lt: start_offset } }).sort({_id: -1}).skip(20*8).limit(1)
  3. 分页数据查询以本页组 start_offset 作为起点,在有限的页码上翻页(skip)
    由于一个分组的数据量通常很小(8*20=160),在分组内进行skip产生的代价会非常小,因此性能上可以得到保证。

小结

随着物联网,大数据业务的白热化,一般企业级系统的数据量也会呈现出快速的增长。而传统的数据库分页方案在海量数据场景下很难满足性能的要求。在本文的探讨中,主要为海量数据的分页提供了几种常见的优化方案(以MongoDB作为实例),并在性能上做了一些对比,旨在提供一些参考。

海量数据的分页怎么破?我的方案这样“改良”,实在太完美! 一、背景 分页应该是极为常见的数据展现方式了,一般在数据集较大而无法在单个页面中呈现时会采用分页的方法。各种前端UI组件在实现上也都会支持分页的功能,而数据交互呈现所相应的后端系统、数据库都对数据查询的分页提供了良好的支持。以几个流行的数据库为例: 查询表 t_data 第 2 页的数据(假定每页 5 条) MySQL 的做法:select * from t_data limit 5,5 Post... 阅读详情

相关推荐

高分7(GF7)卫星数据制作平原地区DSM/DEM(三)——可信度分割

李国春(接上)

副组长的博客 1715

面试官问:数据量很大的情况下,对于分页查询有什么优化方案吗?

当需要从数据库查询的表有上万条记录的时候,一次性查询所有结果会变得很慢,特别是随着数据量的增加特别明显,这时需要使用分页查询。对于数据库分页查询,也有很多种方法和优化的点。下面简单说一下我知道的一些方法。准备工作为了对下面列举的一些优化进行测试,下面针对已有的一张表进行说明。表名:order_history描述:某个业务的订单历史表主要字段:unsigned int id,tinyint(4) i...

Java精选 645

手把手教你在VirtualBox里复活Win98,并搞定驱动和文件传输(含资源寻找攻略)

本文详细指导如何在VirtualBox中安装和配置Windows 98,包括驱动安装、文件传输等实用技巧。通过逐步教程和资源寻找攻略,帮助技术爱好者完美复刻这一经典操作系统,满足怀旧或兼容性测试需求。

weixin_29596051的博客 327

java分页数据过多怎么办_SQL分页过多时, 如何优化

问题:我们经常会使用到分页操作,这里有个问题,在偏移量非常大的时候,它会导致MySQL扫描大量不需要的行然后再抛弃掉。如:SELECT id, name FROM A ORDER BY id DESC LIMIT 10000, 20;上述这条SQL语句需要查询10020条记录然后只返回最后20条。前面的10000条记录都将被抛弃,这样代价非常高。方法一、延迟关联优化此类分类查询的一个最简单的办法就...

weixin_36002349的博客 845

22h2版本的win10安装Docker Desktop时页面空白

摘要:Windows10 22H2系统重装Docker时出现安装界面卡死问题,通过管理员CMD执行静默安装命令解决。命令指定了WSL-2后端及多个自定义数据存储路径(安装目录、Hyper-V数据、Windows容器数据、WSL数据),安装需等待几分钟。最终验证安装成功。

猿小白的博客 664

「JS逆向三百例」JS逆向HIKVISION-视频监控的前端密码加密

本文详细分析了HIKVISION-海康威视监控前端登录过程中密码的加密机制,通过逆向工程解析了其 JavaScript 加密逻辑,重点研究了HIKVISION-海康威视监控的前端密码SHA256加密算法的实现。基于逆向分析结果,使用 Python 模拟了HIKVISION-海康威视监控的登录流程,最终实现了自动化登录功能。本文旨在为安全研究人员提供技术参考,帮助理解前端加密机制,并强调技术研究的合法性和合规性。

布啦啦 1100

数据量过大时的深度分页优化以及前端优化

1.首先我想到的第一种方法是内嵌子查询,查出符合条件的所有id,然后通过id来查询所要的数据,由于id是主键,这样就能走最快的聚簇索引,提高查询速度,优化后的sql查询语句为select * from task where id in (select id from (select id from task where publisherId = 5 limit 1000000,10) as a) order by id。4.另外,还有种更快的优化方式,就是不用limit?

qq_36896711的博客 2195

hadoop 切片机制详解

hadoop切片机制详解

congge 1万+

数据量很大的情况下,分页查询的优化方案

嗨,大家好,欢迎来到公众号,我是漠然。在数据量很大的情况下,分页查询往往会导致查询速度变慢,影响用户体验。作为一Java工程师,我认为可以从以下几个方面来优化分页查询。

weixin_40725027的博客 1437

PCL RANSAC拟合分割圆柱体模型【2026最新版】

使用RANSAC拟合分割圆柱的原理介绍、官网示例代码实现以及一个可视化拟合出来的圆柱的具体应用案例。博客长期更新,最近一次更新时间为:2026年1月28日。

点云侠的博客 40万+

使用HART Server PC软件进行HART设备的组态、调试和分析的完整指南

HART协议是一种用于工业自动化中的双向通信协议,能够在4-20mA模拟信号的基础上叠加数字信号,从而实现设备的远程监控和管理。HART协议兼具模拟和数字通信的优点,被广泛应用于过程控制系统中。设备组态:通过软件可以对HART设备进行组态,配置设备参数。设备调试:支持设备的在线调试和故障诊断,方便快速解决问题。数据分析:提供设备运行数据的实时监控和历史数据分析功能。报表生成:支持生成各类报表,便于数据记录和管理。

qq_38334677的博客 2474

分组后数据 分页_海量数据的分页怎么破?我的方案这样“改良”,实在太完美!...

一、背景分页应该是极为常见的数据展现方式了,一般在数据集较大而无法在单个页面中呈现时会采用分页的方法。各种前端UI组件在实现上也都会支持分页的功能,而数据交互呈现所相应的后端系统、数据库都对数据查询的分页提供了良好的支持。以几个流行的数据库为例:查询表 t_data 第 2 页的数据(假定每页 5 条)MySQL 的做法:select * from t_data limit 5,5PostGreS...

weixin_39968852的博客 388

Android 13.0 framework层系统手势增加上滑手势home事件功能(相当于Home键)

在13.0的定制化开发系统手势功能的时候,客户需求要求在上滑手势的时候,在底部上滑时候进入系统桌面,也就是增加 home键功能,所以就需要分析相关的系统手势上滑事件,然后添加home事件这样 就可以实现这个功能了

安卓兼职framework和app工程师的博客 1133

【华为云技术分享】#华为云·寻找黑马程序员#海量数据的分页怎么破?

一、背景 分页应该是极为常见的数据展现方式了,一般在数据集较大而无法在单个页面中呈现时会采用分页的方法。 各种前端UI组件在实现上也都会支持分页的功能,而数据交互呈现所相应的后端系统、数据库都对数据查询的分页提供了良好的支持。 以几个流行的数据库为例: 查询表 t_data 第 2 页的数据(假定每页 5 条) MySQL 的做法: select * from t_data li...

华为云官方博客 1万+

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

OSS云文件列举分页

前言目前项目开发中上传功能很多都是使用云存储,其好处太多这里就不列举了。但是在上传成功后,有些功能场景下需要对存储桶内的文件进行管理。自然把历史的文件列表展示出来就很有必要了。然后有列表就有分页,云存储又不像本地存储直接读文件夹就可以,所以我想了两种方法来解决。方法将上传的文件返回的地址都存到数据库中,取列表就分页的读表返...

weixin_40370202的博客 220

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

当数据量大时,该如何实现分页功能?

随着系统使用的时间变长,数据库里的数据也越来越多,每次分页查询数据的时间也随之变长,这时候应该怎么做,才能让查询速度更快呢?提示:以下是本篇文章正文内容,下面案例可供参考。

qq_43410943的博客 1069

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

面试官问:MySQL中百万级数据量,如何分页查询?

方法1: 直接使用数据库提供的SQL语句语句样式:MySQL中,可用如下方法: SELECT * FROM 表名称 LIMIT M,N适应场景:适用于数据量较少的情况(元组百/千级)原因/缺点:全表扫描,速度会很慢 且 有的数据库结果集返回不稳定(如某次返回1,2,3,另外的一次返回2,1,3). Limit限制的是从结果集的M位置处取出N条输出,其余抛弃.方法2:建立主键或唯一索引, 利...

Java精选 2480

mysql超大分页优化

在 MySQL 中处理超大分页时,传统的方法并不适合大数据量场景,优化方式需根据实际需求选择。本文介绍的索引优化、覆盖索引、延迟关联等方法均可以有效提升查询性能。此外,结合 Java 实现的分页代码展示了具体的应用场景。对于需要极深分页的场景,建议结合缓存或限制分页范围的方式进行合理设计,以平衡性能与用户体验。

Flying_Fish_roe的博客 1849

MySQL超大分页处理,以及优化思路说明。

MySQL超大分页处理,以及优化思路说明。

m0_56615376的博客 1739

大数据量的分页,怎么办?

分页功能这个是大家工作中经常碰到的,相信大家脑海中直接跳出limit offset,size . 当进行深度分页时会卡崩,为什么?因为执行这条sql需要先读取offset+size条数据,根据实际业务在需要进行排序,然后丢弃到offset条数据。例如:假设有一张表,三个字段,分别为id,name,age. 其中id是主键。age 普通索引。需求:我们需要查找出按照年龄排序offset=1000000 size =10 的记录。如果使用limit offset,size,需要进行次操作。

m0_37242314的博客 1083

超大分页怎么处理?

的方式进行分页会随着offset值的增大导致查询性能急剧下降。这是因为数据库需要扫描从第一条记录开始跳过大量的行才能达到指定的偏移量(OFFSET),这对于数据库来说是非常低效的操作。综合以上策略,应根据具体业务场景选择最适合的方法来解决超大分页带来的性能问题。处理MySQL超大分页的问题,通常是因为当数据量非常庞大时,直接使用。

学亮编程手记 863

超大分页怎么处理?

1、这条语句需要load1000000数据然后只取10条,其他全部丢弃,当然比较慢. 我们可以修改为select * from table where id in (select id from table where age> 20 limit 1000000,10).这样虽然也load了一百万的数据,但是由于索引覆盖,要查询的所有字段都在索引中,所以速度会很快.主要是不做类似的需,只允许逐页查看或者按照给定的路线走。在阿里巴巴《Java开发手册》中,对超大分页的解决办法是类似于上面提到的第一种。

qq_33240556的博客 705

400万条数据 前端已经有分页的情况下 还有什么优化

【代码】400万条数据 前端已经有分页的情况下 还有什么优化。

qq_51398495的博客 849

Mysql 超大分页怎么处理?

MySQL处理超大分页时遇到的问题类似,如果你请求查看结果集中的第几万甚至第几十万页数据,直接用传统的LIMIT offset, rows方法(比如跳过前100000行,取10行)会非常慢,因为它实际上是从头开始数,丢掉前面的100000行,再给你后面的数据。比如,记录下上次读到的是第100000条记录,下次查询时,直接说“从第100001条开始给我10条”,这样数据库就可以直接定位,不用从头开始数了。用这些方法,MySQL在面对超大分页时,就能更快地跳到你想要的那一页,不用再一页一页慢慢翻了。

qq_33449977的博客 1467

MySQL之如何处理超大分页

在数据量较大时,如果使用limit分页查询,在查询时,越往后,分页查询效率会越低。此时通过“覆盖索引+子查询”的形式进行优化。

心有谦谦结 1130

MySQL/Mongo大数据量分页查询优化

分页的2种写法 SELECT keyword FROM keyword_rank WHERE advertiserid='59' order by keyword LIMIT 2 OFFSET 1; 比如这个SQL ,limit后面跟的是2条数据,offset后面是从第1条开始读取 SELECT keyword FROM keyword_rank WHERE advertiserid=...

hudeyong926的专栏 1348
上一篇: 网易CEO重磅推荐,这本书带你了解SQL编程的奥秘,演绎SQL编程之美!
下一篇: 大厂的人自带光环,但光环是从哪儿来的呢?——一位中年Java程序员的感慨
JavaCaiy
博客等级 码龄6年 1万+粉丝 95原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值