当前位置: 首页 > news >正文

高效分页策略:掌握 LIMIT 语句的正确使用方法与最佳实践

本文主要介绍limit 分页的弊端及线上应该怎么用

LIMIT M,N

平时经常见到使用 <limit m,n>+ 合适的 order by 来实现分页查询,这样做到底性能如何呢?

先来简单分析下,然后再实际验证一下。

  1. 无索引条件下,需要做大量的文件排序操作,性能将会非常糟糕;
  2. 有索引条件下,刚开始的分页查询效率会比较理想,但越往后,分页查询的性能就越差。

这主要是因为,在使用 LIMIT 的时候,偏移量 M 在分页越靠后的时候,值就越大,数据库检索的数据也就越多。
例如 LIMIT 90000,10 这样的查询,数据库需要查询 90010 条记录,最后返回 10 条记录。也就是说将会有 90000 条记录被查询出来没有被使用到。

下面我们来验证下
首先创建一张会员表,表结构如下

CREATE TABLE `member` (`id` int(11) NOT NULL AUTO_INCREMENT,`member_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,`member_phone` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,`join_date` datetime DEFAULT CURRENT_TIMESTAMP,`member_id` bigint(20) NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `idx_member_id` (`member_id`)
)

插入 10 万条数据

DELIMITER //CREATE PROCEDURE InsertMember()
BEGINDECLARE i INT DEFAULT 0;WHILE i < 100000 DO-- 为member_id生成一个10位随机数SET @random_member_id = FLOOR(RAND() * 9000000000 + 1000000000) + i*RAND();-- 插入数据INSERT INTO member (member_name, member_phone, member_id)VALUES (CONCAT('Member', LPAD(i + 1, 5, '0')), -- 会员姓名,编号后面跟5个0CONCAT('13', LPAD(RAND()*(9999999999-1000000000+1)+1000000000, 10, '0')), -- 随机生成电话号码@random_member_id -- 随机生成的会员编号);-- 增加循环计数器SET i = i + 1;END WHILE;
END //DELIMITER ;

执行存储过程

CALL InsertMember();

验证 limit 查询

执行sql

select * from member order by member_id limit 90000, 10;

limit 分页

可以看到,所用查询时间为 0.227s,相对来说时间偏长了。

子查询优化

先查询出所需要的 10 行数据中的最小 ID 值,然后通过偏移量返回所需要的 10 行数据,可以通过索引覆盖扫描,使用子查询的方式来实现分页查询

SELECT *
FROMmember
WHEREid > (SELECT idFROMmemberORDER BY member_idLIMIT 90000 , 1)
LIMIT 10;

子查询优化
执行时间 0.024s

线上分页

那么在实际的生产环境中,该怎么使用呢?下面我来介绍下我当时是怎么做的。
核心思想就是:分段查询

假如有个订单表,在 【2024-01-01 00:00:00,2024-01-02 00:00:00】有12万条数据, 前 11 个小时段有接近于 1 万条数据,第 12 个小时段有大于 1 万条数据。

现在我们采用分时间段查询,间隔为 1 小时,每次查询 2000 条,那么每个小时段需要查询 5-6次。

先贴出 SQL 代码,方便查看

<select id="grabBizDataSlice" resultMap="BaseResultMap">select<include refid="Base_Column_List"/>from orderwhere update_time &gt;= \#{startTime} and update_time &lt; \#{endTime}and status = 'PROCESS'and id > \#{startRow}order by idlimit  \#{pageSize}</select>

第一个小时

第一次查询

时间段:【2024-01-01 00:00:00,2024-01-01 01:00:00】,
startRow:0
pageSize:2000

第二次查询

时间段:【2024-01-01 00:00:00,2024-01-01 01:00:00】,
startRow:2000
pageSize:2000

第三次查询

时间段:【2024-01-01 00:00:00,2024-01-01 01:00:00】,
startRow: 4000
pageSize:2000

第四次查询

时间段:【2024-01-01 00:00:00,2024-01-01 01:00:00】,
startRow:6000
pageSize:2000

第五次查询

时间段:【2024-01-01 00:00:00,2024-01-01 01:00:00】,
startRow:> 8000
pageSize:2000

注意:第 5 次查询的时候,实际返回的数据量总量已经小于 2000 条了,此时我们就可以判断到第一个小时段的数据已经查询结束了,然后开始第二个时间段的查询,道理是一样的。

redis 存储分段条件

通过上面可以看出来,我们需要有一个地方来保存每次查询的条件的。
这里我是采用的 redis hash 结构。

private final  Map<String, String> bizIdxKeyMap = new HashMap<>();
private final Integer pageSize = 2000;List<B> bizDataList = ...; //从数据库查询的记录
Long pageIdx = bizDataList.size() == pageSize ? bizDataList.get(pageSize - 1).getId() : -1;
bizIdxKeyMap.put("sliceStartCache", sliceStartTime);
bizIdxKeyMap.put("sliceEndCache", sliceEndTime);
bizIdxKeyMap.put("pageIdxCache", pageIdx.toString());
redisCluster.hmset(bizIdxKey, bizIdxKeyMap);

从这里可以看到,当pageIdx = -1时,代表本时间段查询结束了。在下次循环时,再从 redis 中取出来这三个字段 sliceStartCachesliceEndCachepageIdxCache

完整代码

class InitController {@Autowiredprivate BizCommonService bizCommonService;@Autowiredprivate OrderInitServiceImpl orderInitServiceImpl;void calculationFlow(Date startTime, Date endTime) {bizCommonService.initFinanceCalculationCycle(startTime, endTime);orderInitServiceImpl.orderInit();}
}@Service
public class OrderInitServiceImpl{@Autowiredprivate OdsPackOrderDAO odsPackOrderDAO;@Autowiredprivate BizCommonService  bizCommonService;public void orderInit() throws InterruptedException {while(true){String packOrderCalculationSwitch = redisCluster.get("pack_order_switch");if(packOrderCalculationSwitch != null && packOrderCalculationSwitch.equals("switch_off")){break; //查询结束}List<OdsPackOrderDO> odsPackOrderDOList = bizCommonService.grabBizDataSlice(3,TimeUnit.MINUTES, 2000, odsPackOrderDAO, null);// 对查询出来的odsPackOrderDOList做一些业务逻辑}}}@Component
public class BizCommonServicelImpl{@Autowiredprotected RedisCluster redisCluster;private Date financeCycleStartTime;private Date financeCycleEndTime;private final  Map<String, String> bizIdxKeyMap = new HashMap<>();private final static Calendar calendar= Calendar.getInstance();public void initFinanceCalculationCycle(Date startTime, Date endTime) {this.financeCycleStartTime = startTime;this.financeCycleEndTime = endTime;}public List<B> grabBizData(@NonNull Integer interval, TimeUnit intervalUnit, @NonNull Integer pageSize, BD bizDataSource, @Nullable Object customParam){try{String bizIdxKey = "order_index_key"; // 分页条件键String bizSwitchKey = "pack_order_switch"; // 查询终止状态键// 从 redis 查询分页条件键List<String> bizIdxCache = redisCluster.hmget(bizIdxKey, "sliceStartCache", "sliceEndCache", "pageIdxCache");Long pageIdx;Date sliceEndTime;Date sliceStartTime;if(bizIdxCache.get(2) == null ||  bizIdxCache.get(2).equals("-1")){pageIdx = 0L;if(bizIdxCache.get(0) == null){sliceStartTime = financeCycleStartTime;sliceEndTime = timer(sliceStartTime, interval, intervalUnit);}else{sliceStartTime = DateUtils.getDateByMySQLDateTimeString(bizIdxCache.get(1));sliceEndTime = timer(sliceStartTime, interval, intervalUnit);}}else{sliceStartTime = DateUtils.getDateByMySQLDateTimeString(bizIdxCache.get(0));sliceEndTime = DateUtils.getDateByMySQLDateTimeString(bizIdxCache.get(1));pageIdx = Long.valueOf(bizIdxCache.get(2));}// 判断结束标志if(sliceStartTime != null && (sliceStartTime.after(financeCycleEndTime) || sliceStartTime.equals(financeCycleEndTime))){redisCluster.set("pack_order_switch", SWITCH_OFF);return null;}List<B> bizDataList;if(customParam == null) {bizDataList = bizDataSource.grabBizDataSlice(sliceStartTime,sliceEndTime.after(financeCycleEndTime) ? financeCycleEndTime : sliceEndTime,pageIdx,pageSize);}else{bizDataList = bizDataSource.grabBizDataSliceByCustomParam(sliceStartTime,sliceEndTime.after(financeCycleEndTime) ? financeCycleEndTime : sliceEndTime,pageIdx,pageSize,customParam);}pageIdx = bizDataList.size() == pageSize ? bizDataList.get(pageSize - 1).getId() : -1;bizIdxKeyMap.put("sliceStartCache", sliceStartTime);bizIdxKeyMap.put("sliceEndCache", sliceEndTime);bizIdxKeyMap.put("pageIdxCache", pageIdx.toString());redisCluster.hmset("order_index_key", bizIdxKeyMap);return bizDataList;}catch (Exception e){return null;}}private Date timer(Date currentTime, Integer interval, TimeUnit intervalUnit){calendar.setTime(currentTime);if(intervalUnit == TimeUnit.DAYS){calendar.add(Calendar.DATE, interval);}else if(intervalUnit == TimeUnit.HOURS){calendar.add(Calendar.HOUR, interval);}else if(intervalUnit == TimeUnit.MINUTES){calendar.add(Calendar.MINUTE, interval);}else if(intervalUnit == TimeUnit.SECONDS){calendar.add(Calendar.SECOND, interval);}else {throw new RuntimeException("");}return calendar.getTime();}
}

总结

采取合理的分页方式可以有效的提升系统性能,应根据实际情况选择适合自己的方式。
欢迎各位老师分享工作中是怎么使用的,可以交流交流。

相关文章:

高效分页策略:掌握 LIMIT 语句的正确使用方法与最佳实践

本文主要介绍limit 分页的弊端及线上应该怎么用 LIMIT M,N 平时经常见到使用 <limit m,n> 合适的 order by 来实现分页查询&#xff0c;这样做到底性能如何呢&#xff1f; 先来简单分析下&#xff0c;然后再实际验证一下。 无索引条件下&#xff0c;需要做大量的文件排…...

拼图游戏02

文章目录 概要整体架构流程代码过程小结 概要 现在需要将图片添加界面中 关键点在于它如何动态地根据游戏状态更新用户界面。它使用了Swing的布局管理器来定位组件&#xff0c;并且通过ImageIcon和JLabel来显示图像。注意&#xff0c;路径字符串中的反斜杠在Java中是转义字符…...

在本地进行Django支付宝扫码支付-当面付开发

这几天涉及到一个个人项目的支付开发场景&#xff0c;正好完成之后&#xff0c;做一下开发记录&#xff0c;给有需要的朋友做一下参考 涉及安装Python环境请参考我专栏中的历史文章&#xff0c;这里不再重复说明 环境&#xff1a; Python3.11 使用Django框架 因本次代码为沙…...

redis-RedisTemplate.opsForGeo 的geo地理位置相关的方法演示

主要方法&#xff1a;add : 添加一个地理位置distance: 计算两个元素之间的距离hash&#xff1a; 获取元素经纬度坐标经过geohash算法生成的base32编码值position: 获取集合中任意元素的经纬度坐标&#xff0c;可以一次获取多个radius&#xff1a;查询某个坐标或某个成员&#…...

做短视频矩阵要十几人团队吗?云微客助阵,一人即可

现在市面上主流的新媒体平台都进军了短视频赛道&#xff0c;对于众多企业和个人来说&#xff0c;短视频矩阵更是成为了提升影响力和拓展业务的关键。企业或个人可以根据自身产品特点和目标用户群体&#xff0c;构建账号矩阵&#xff0c;在多平台上建立账号矩阵&#xff0c;还可…...

常用语音识别开源工具的对比与实践

常用语音识别开源工具的对比 一.工具概述 1. WeNet 设计目标&#xff1a;WeNet 的设计主要聚焦于端到端&#xff08;E2E&#xff09;语音识别&#xff0c;特别是在流式识别方面的优化。其目标是提供一个可以在实际应用中达到低延迟和高精度的系统。模型架构&#xff1a; Con…...

Fortify代码安全测试工具在静态应用安全测试(SAST)方面针对典型问题的改进

Fortify代码安全测试工具作为行业内资深的老牌软件安全测试工具&#xff0c;可以同时支持静态代码扫描和动态代码扫描&#xff0c;本文我们讲述的主要是在静态代码扫描领域Fortify所面临的问题&#xff0c;以及最新的改进。 在应用安全领域&#xff0c;特别是静态应用安全测试&…...

AWS 消息队列服务 SQS

AWS 消息队列服务 SQS 引言什么是 SQSSQS 访问策略 Access Policy示例&#xff1a;如何为 DataLake Subscription 配置 SQS 引言 应用系统需要处理海量数据&#xff0c;数据发送方和数据消费方是通过什么方式来无缝集成消费数据的&#xff0c;AWS 提供 SQS 消息队列服务来解决…...

【iOS】——响应者链和事件传递链

事件传递 事件传递流程 发生触摸事件后&#xff0c;系统会将该事件封装成UIEvent对象加入到一个由UIApplication管理的事件队列 UIApplication会从事件队列中取出最前面的事件&#xff0c;并将事件分发下去以便处理&#xff0c;通常&#xff0c;先发送事件给应用程序的主窗口…...

mysql查询慢

可能是连接数&#xff0c;或者缓存不够&#xff0c;可尝试添加如下参数&#xff0c;重启mysql [mysqld] max_connections50000 interactive_timeout604800 lock_wait_timeout600 wait_timeout604800 net_read_timeout604800 log-error/var/lib/mysql/mysqld.log slow_qu…...

【Java-==与equals】

与equals区别&#xff1a; 1.是关系运算符&#xff0c;equals()是0bject类中定义的方法 2.基本数据类型: 使用比较值&#xff0c;无法使用equals() 3.引用数据类型: 使用比较内存地址; 如果没有重写equals(),仍然调用的是0bject父类的equals(()方法&#xff0c;则比较的是内…...

ai回答 部署前端项目时需要使用ssh吗

SSH&#xff08;Secure Shell&#xff09;是一种网络协议&#xff0c;用于在不安全的网络上安全地访问远程计算机。SSH 提供了加密的命令行登录到远程计算机的功能&#xff0c;以及远程命令执行和其他服务。它常用于系统管理员管理服务器、开发者进行远程开发、用户通过终端访问…...

结合ChatGPT与Discord,提高团队合作效率

本文将教你如何集成Discord Bot&#xff0c;助力团队在工作中实现更高效的沟通与协作。通过充分发挥ChatGPT的潜力&#xff0c;进一步提升工作效率和团队协作能力。无需编写任何代码即可完成本文所述的操作&#xff0c;进行个性化定制只需对参数进行微调即可。 方案介绍 如果在…...

VisualStudio|开发环境相关技巧及问题

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 本节继续学习VisualStudio相关内容&#xff0c;以前学习都是以能用为主&#xff0c;没有系统的学习&#xff0c;接下来会系统的学习相关内容&#xff0c; 以下为学习笔记。 01 第三方dll调用 ①&#xff1a;如果第三…...

Redis远程字典服务器(11)—— redis客户端介绍

一&#xff0c;基本介绍 前面学习的主要是各种Redis的基本操作/命令&#xff0c;都是再Redis命令行客户端&#xff0c;手动执行的&#xff0c;但是这种方式不是我们日常开发中主要的形式更多的时候&#xff0c;是使用Redis的api&#xff0c;来实现定制化的Redis客户端程序&…...

【mysql】mysql之DDL数据定义语言

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…...

Word文件密码忘记,该如何才能编辑Word文件呢?

Word文件打开之后&#xff0c;发现编辑功能都是灰色的&#xff0c;无法使用&#xff0c;无法编辑&#xff0c;遇到这种情况&#xff0c;是因为Word文件设置了限制编辑导致的。一般情况下&#xff0c;我们只需要输入Word密码&#xff0c;将限制编辑取消就可以正常编辑文件了&…...

解锁移动办公新境界,七款顶尖移动终端管控软件分享!助您轻松掌控每一台移动设备,企业必备!

移动办公&#xff0c;它不仅打破了时间和空间的限制&#xff0c;提高了工作效率&#xff0c;还为员工创造了更加灵活的工作环境。然而&#xff0c;随着移动设备的普及&#xff0c;如何有效管理和控制这些终端&#xff0c;确保信息安全、提升工作效率呢&#xff1f; 今天&#…...

基于微信小程序的大用户心理咨询系统设计与实现---附源码99040

目录 1 绪论 1.1 研究背景 1.2研究现状 1.3论文结构与章节安排 2 基于微信小程序的大用户心理咨询系统设计与实现分析 2.1 可行性分析 2.2 系统功能分析 2.3 系统用例分析 2.4 系统流程分析 2.5本章小结 3 基于微信小程序的大用户心理咨询系统设计与实现总体设计 3.…...

Bigtop 从0开始(上)

本文作者&#xff1a;蔡佳良 原文阅读&#xff1a;【巨人肩膀社区博客分享】Bigtop 从0开始 BigTop的应用场景&#xff1a; 1. BigTop通过提供预配置的Docker镜像&#xff0c;极大简化了在不同操作系统上编译大数据组件的rpm或deb包的过程&#xff0c;使之变得快捷且高效。 …...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

Admin.Net中的消息通信SignalR解释

定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具&#xff0c;可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件&#xff0c;也不需要在线上传文件&#xff0c;保护您的隐私。 工具截图 主要特点 &#x1f680; 快速转换&#xff1a;本地转换&#xff0c;无需等待上…...

基于IDIG-GAN的小样本电机轴承故障诊断

目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) ​梯度归一化(Gradient Normalization)​​ (2) ​判别器梯度间隙正则化(Discriminator Gradient Gap Regularization)​​ (3) ​自注意力机制(Self-Attention)​​ 3. 完整损失函数 二…...

PostgreSQL——环境搭建

一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在&#xff0…...

Linux系统部署KES

1、安装准备 1.版本说明V008R006C009B0014 V008&#xff1a;是version产品的大版本。 R006&#xff1a;是release产品特性版本。 C009&#xff1a;是通用版 B0014&#xff1a;是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存&#xff1a;1GB 以上 硬盘&#xf…...

Leetcode33( 搜索旋转排序数组)

题目表述 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...

【堆垛策略】设计方法

堆垛策略的设计是积木堆叠系统的核心&#xff0c;直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法&#xff0c;涵盖基础规则、优化算法和容错机制&#xff1a; 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则&#xff1a; 大尺寸/重量积木在下&#xf…...