高效分页策略:掌握 LIMIT 语句的正确使用方法与最佳实践
本文主要介绍limit 分页的弊端及线上应该怎么用
LIMIT M,N
平时经常见到使用 <limit m,n>+ 合适的 order by 来实现分页查询,这样做到底性能如何呢?
先来简单分析下,然后再实际验证一下。
- 无索引条件下,需要做大量的文件排序操作,性能将会非常糟糕;
- 有索引条件下,刚开始的分页查询效率会比较理想,但越往后,分页查询的性能就越差。
这主要是因为,在使用 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;

可以看到,所用查询时间为 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 >= \#{startTime} and update_time < \#{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 中取出来这三个字段 sliceStartCache、sliceEndCache、pageIdxCache。
完整代码
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 来实现分页查询,这样做到底性能如何呢? 先来简单分析下,然后再实际验证一下。 无索引条件下,需要做大量的文件排…...
拼图游戏02
文章目录 概要整体架构流程代码过程小结 概要 现在需要将图片添加界面中 关键点在于它如何动态地根据游戏状态更新用户界面。它使用了Swing的布局管理器来定位组件,并且通过ImageIcon和JLabel来显示图像。注意,路径字符串中的反斜杠在Java中是转义字符…...
在本地进行Django支付宝扫码支付-当面付开发
这几天涉及到一个个人项目的支付开发场景,正好完成之后,做一下开发记录,给有需要的朋友做一下参考 涉及安装Python环境请参考我专栏中的历史文章,这里不再重复说明 环境: Python3.11 使用Django框架 因本次代码为沙…...
redis-RedisTemplate.opsForGeo 的geo地理位置相关的方法演示
主要方法:add : 添加一个地理位置distance: 计算两个元素之间的距离hash: 获取元素经纬度坐标经过geohash算法生成的base32编码值position: 获取集合中任意元素的经纬度坐标,可以一次获取多个radius:查询某个坐标或某个成员&#…...
做短视频矩阵要十几人团队吗?云微客助阵,一人即可
现在市面上主流的新媒体平台都进军了短视频赛道,对于众多企业和个人来说,短视频矩阵更是成为了提升影响力和拓展业务的关键。企业或个人可以根据自身产品特点和目标用户群体,构建账号矩阵,在多平台上建立账号矩阵,还可…...
常用语音识别开源工具的对比与实践
常用语音识别开源工具的对比 一.工具概述 1. WeNet 设计目标:WeNet 的设计主要聚焦于端到端(E2E)语音识别,特别是在流式识别方面的优化。其目标是提供一个可以在实际应用中达到低延迟和高精度的系统。模型架构: Con…...
Fortify代码安全测试工具在静态应用安全测试(SAST)方面针对典型问题的改进
Fortify代码安全测试工具作为行业内资深的老牌软件安全测试工具,可以同时支持静态代码扫描和动态代码扫描,本文我们讲述的主要是在静态代码扫描领域Fortify所面临的问题,以及最新的改进。 在应用安全领域,特别是静态应用安全测试&…...
AWS 消息队列服务 SQS
AWS 消息队列服务 SQS 引言什么是 SQSSQS 访问策略 Access Policy示例:如何为 DataLake Subscription 配置 SQS 引言 应用系统需要处理海量数据,数据发送方和数据消费方是通过什么方式来无缝集成消费数据的,AWS 提供 SQS 消息队列服务来解决…...
【iOS】——响应者链和事件传递链
事件传递 事件传递流程 发生触摸事件后,系统会将该事件封装成UIEvent对象加入到一个由UIApplication管理的事件队列 UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口…...
mysql查询慢
可能是连接数,或者缓存不够,可尝试添加如下参数,重启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区别: 1.是关系运算符,equals()是0bject类中定义的方法 2.基本数据类型: 使用比较值,无法使用equals() 3.引用数据类型: 使用比较内存地址; 如果没有重写equals(),仍然调用的是0bject父类的equals(()方法,则比较的是内…...
ai回答 部署前端项目时需要使用ssh吗
SSH(Secure Shell)是一种网络协议,用于在不安全的网络上安全地访问远程计算机。SSH 提供了加密的命令行登录到远程计算机的功能,以及远程命令执行和其他服务。它常用于系统管理员管理服务器、开发者进行远程开发、用户通过终端访问…...
结合ChatGPT与Discord,提高团队合作效率
本文将教你如何集成Discord Bot,助力团队在工作中实现更高效的沟通与协作。通过充分发挥ChatGPT的潜力,进一步提升工作效率和团队协作能力。无需编写任何代码即可完成本文所述的操作,进行个性化定制只需对参数进行微调即可。 方案介绍 如果在…...
VisualStudio|开发环境相关技巧及问题
哈喽,你好啊,我是雷工! 本节继续学习VisualStudio相关内容,以前学习都是以能用为主,没有系统的学习,接下来会系统的学习相关内容, 以下为学习笔记。 01 第三方dll调用 ①:如果第三…...
Redis远程字典服务器(11)—— redis客户端介绍
一,基本介绍 前面学习的主要是各种Redis的基本操作/命令,都是再Redis命令行客户端,手动执行的,但是这种方式不是我们日常开发中主要的形式更多的时候,是使用Redis的api,来实现定制化的Redis客户端程序&…...
【mysql】mysql之DDL数据定义语言
本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》:python零基础入门学习 《python运维脚本》: python运维脚本实践 《shell》:shell学习 《terraform》持续更新中:terraform_Aws学习零基础入门到最佳实战 《k8…...
Word文件密码忘记,该如何才能编辑Word文件呢?
Word文件打开之后,发现编辑功能都是灰色的,无法使用,无法编辑,遇到这种情况,是因为Word文件设置了限制编辑导致的。一般情况下,我们只需要输入Word密码,将限制编辑取消就可以正常编辑文件了&…...
解锁移动办公新境界,七款顶尖移动终端管控软件分享!助您轻松掌控每一台移动设备,企业必备!
移动办公,它不仅打破了时间和空间的限制,提高了工作效率,还为员工创造了更加灵活的工作环境。然而,随着移动设备的普及,如何有效管理和控制这些终端,确保信息安全、提升工作效率呢? 今天&#…...
基于微信小程序的大用户心理咨询系统设计与实现---附源码99040
目录 1 绪论 1.1 研究背景 1.2研究现状 1.3论文结构与章节安排 2 基于微信小程序的大用户心理咨询系统设计与实现分析 2.1 可行性分析 2.2 系统功能分析 2.3 系统用例分析 2.4 系统流程分析 2.5本章小结 3 基于微信小程序的大用户心理咨询系统设计与实现总体设计 3.…...
Bigtop 从0开始(上)
本文作者:蔡佳良 原文阅读:【巨人肩膀社区博客分享】Bigtop 从0开始 BigTop的应用场景: 1. BigTop通过提供预配置的Docker镜像,极大简化了在不同操作系统上编译大数据组件的rpm或deb包的过程,使之变得快捷且高效。 …...
Codex入门09-Git工作流(小白入门:不会写commit信息?AI帮你自动生成规范提交)
🎯 本文目标 学会用 Codex 自动化 Git 操作:提交、冲突解决、PR 描述生成。 😰 Git 新手的典型痛点 你的提交记录是不是这样的: git log --oneline a3f4b2c fix 9d1e8c4 update 4c7b91f 修改了一些东西 f0a2d3e 。。。 b5c8e7a 又改了这就是"屎山提交记录"—…...
嵌入式开发实战:从ADC纹波故障看系统集成调试与EMC设计
1. 项目背景与问题缘起:当“新”设备遭遇“老”问题在工业设备开发领域,尤其是像线锯这类集精密机械、复杂电气和嵌入式软件于一体的复杂系统,有一个经典且令人头疼的场景:一款经过验证的成熟产品平台,在衍生出新机型或…...
如何永久保存微信聊天记录:5分钟学会WeChatMsg免费完整指南
如何永久保存微信聊天记录:5分钟学会WeChatMsg免费完整指南 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we/…...
Cache映射策略全解析:从全相联到组相连,如何平衡灵活性与效率?
1. 为什么需要Cache映射策略? 想象一下你正在图书馆找一本书。如果每次都要从最外层的书架开始一本本翻找,效率肯定低得令人发指。这时候我们会给书籍分类编号——这就是Cache映射策略的日常类比。 在计算机体系结构中,CPU的运行速度远远快于…...
基于MCP与Apify的ESG供应链风险智能评估工具实战指南
1. 项目概述:一个为AI工作流赋能的ESG供应链风险智能评估工具 如果你是一名ESG分析师、供应链合规官或者投资经理,那么你一定对“供应商ESG尽职调查”这件事又爱又恨。爱的是,它确实能帮你识别潜在的环境、社会和治理风险,避免“…...
AI Agent开发实战:从核心范式到工程落地的完整指南
1. 项目概述:一场静悄悄的技术代际更迭最近和几个技术团队负责人聊天,话题总绕不开“AI Agent”。大家的感觉出奇地一致:这玩意儿的发展速度,快得有点让人喘不过气。新闻里、论文里、各种技术峰会上,关于智能体&#x…...
如何解决QQ音乐下载的歌曲在其他设备上无法播放的问题
如何解决QQ音乐下载的歌曲在其他设备上无法播放的问题 【免费下载链接】qmcflac2mp3 直接将qmcflac文件转换成mp3文件,突破QQ音乐的格式限制 项目地址: https://gitcode.com/gh_mirrors/qm/qmcflac2mp3 你是否曾经在QQ音乐下载了喜欢的歌曲,却发现…...
RuoYi Office 企业多端协同办公落地实战
很多企业在推进数字化办公时,常陷入一个尴尬的境地:PC 端的管理后台功能强大但操作繁琐,移动端的小程序或 App 虽然便捷却数据割裂。HR 在电脑上录入的员工档案,销售在手机里看不到;老板在微信上审批的流程,…...
3步终极指南:用开源TCC-G15彻底解决Dell G15散热难题
3步终极指南:用开源TCC-G15彻底解决Dell G15散热难题 【免费下载链接】tcc-g15 Thermal Control Center for Dell G15 - open source alternative to AWCC 项目地址: https://gitcode.com/gh_mirrors/tc/tcc-g15 你是否正在为Dell G15笔记本的过热问题而烦恼…...
内向技术人突破领导力瓶颈:从深度思考到战略沟通的进阶指南
1. 项目概述:内向工程师的“天花板”与破局之路 在技术圈子里待久了,你会发现一个有趣的现象:身边那些能写出精妙算法、搞定复杂架构的工程师,往往在茶水间的闲聊中显得沉默寡言,在大型会议上也更倾向于坐在后排。这并…...
