高效分页策略:掌握 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包的过程,使之变得快捷且高效。 …...
“全民补贴”别再烧钱了!
我用3个真实案例,拆透“补贴变投资”的底层逻辑上周和做本地生活服务的张总撸串,他灌了口啤酒直摇头:“以前搞‘满100减30’补贴,用户薅完羊毛就跑,3个月烧了50万,复购率反倒跌了10%——这补贴到底该怎么玩…...
nomic-embed-text-v2-moe部署教程:Nginx反向代理+HTTPS配置保障生产环境安全
nomic-embed-text-v2-moe部署教程:Nginx反向代理HTTPS配置保障生产环境安全 1. 开篇:为什么你的AI模型需要一个“门卫”? 想象一下,你刚把一台功能强大的AI服务器部署在公司内网,准备用它来处理各种文本分析任务。结…...
腾讯云/阿里云服务器上,用娃娃一键端30分钟搞定DNF私服(附端口安全组避坑指南)
腾讯云/阿里云30分钟极速部署DNF私服全攻略:从安全组配置到五国启动 最近在游戏开发者社区里,不少朋友都在讨论如何在云服务器上快速搭建DNF私服体验服。作为一名长期混迹于各类游戏私服搭建的老玩家,我发现大多数教程要么过于专业化…...
不止于采集:用BrainFlow解锁DeepBCI脑电信号的进阶玩法(特征提取与简单分类)
不止于采集:用BrainFlow解锁DeepBCI脑电信号的进阶玩法(特征提取与简单分类) 当你已经能够稳定采集到DeepBCI设备的脑电信号时,那些跳动的波形背后隐藏着怎样的秘密?本文将带你跨越数据采集的门槛,探索如何…...
CTF选手必看:RSA算法从数学原理到实战解题技巧(附常见题型解析)
CTF选手必看:RSA算法从数学原理到实战解题技巧(附常见题型解析) 1. RSA算法核心数学原理 RSA算法的安全性建立在大整数分解难题和欧拉定理之上。理解以下数学概念是解题基础: 欧拉函数φ(n):对于npq(p、q为…...
别再死记硬背了!用‘抽奖游戏’和‘股票涨跌’轻松搞懂马尔可夫链的几种变体
用生活故事解锁马尔可夫链的三种高级玩法 想象你正站在商场抽奖转盘前,每次转动都可能改变你的命运——这像极了马尔可夫链中状态的随机跃迁。但真实世界远比简单转盘复杂:朋友的喜怒无常像隐藏在表情背后的秘密(隐马尔可夫模型)…...
北斗网格位置码实战:从编码原理到Java实现(非极地)
1. 北斗网格位置码:为什么我们需要它? 当你打开手机地图查看自己的位置时,看到的通常是经纬度坐标。这种表示方式虽然精确,但在实际应用中却存在不少问题。比如在物流配送系统中,直接存储和查询经纬度数据效率很低&am…...
Compiler Explorer安全防护终极指南:7个关键步骤保护你的编译环境
Compiler Explorer安全防护终极指南:7个关键步骤保护你的编译环境 【免费下载链接】compiler-explorer Run compilers interactively from your web browser and interact with the assembly 项目地址: https://gitcode.com/gh_mirrors/co/compiler-explorer …...
Ip2region终极指南:如何快速部署高性能离线IP定位系统
Ip2region终极指南:如何快速部署高性能离线IP定位系统 【免费下载链接】ip2region Ip2region (2.0 - xdb) 是一个离线IP地址管理与定位框架,能够支持数十亿级别的数据段,并实现十微秒级的搜索性能。它为多种编程语言提供了xdb引擎实现。 项…...
使用Proteus仿真结合RWKV7-1.5B-G1A:模拟智能硬件对话系统
使用Proteus仿真结合RWKV7-1.5B-G1A:模拟智能硬件对话系统 1. 项目概述 在物联网和智能硬件快速发展的今天,如何让硬件设备具备更自然的交互能力成为一个有趣的研究方向。本文将展示一个跨学科创意项目:在Proteus仿真环境中搭建包含MCU和外…...
