MySQL调优 - SQL查询深度分页问题

news2024/11/25 10:07:11

一、问题引入

例如当前存在一张表test_user,然后往这个表里面插入3百万的数据:

CREATE TABLE `test_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `user_id` varchar(36) NOT NULL COMMENT '用户id',
  `user_name` varchar(30) NOT NULL COMMENT '用户名称',
  `phone` varchar(20) NOT NULL COMMENT '手机号码',
  `lan_id` int(9) NOT NULL COMMENT '本地网',
  `region_id` int(9) NOT NULL COMMENT '区域',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT;

在数据库开发过程中我们经常会使用分页,核心技术是使用用 limit start, count 分页语句进行数据的读取。 

我们分别看下从0、10000、100000、500000、1000000、1800000开始分页的执行时长(每页取100条)。

SELECT * FROM test_user LIMIT 0,100;         # 0.031
SELECT * FROM test_user LIMIT 10000,100;     # 0.047
SELECT * FROM test_user LIMIT 100000,100;    # 0.109
SELECT * FROM test_user LIMIT 500000,100;    # 0.219
SELECT * FROM test_user LIMIT 1000000,100;   # 0.547s
SELECT * FROM test_user LIMIT 1800000,100;   # 1.625s

我们已经看出随着起始记录的增加,时间也随着增大。这说明分页语句limit跟起始页码是有很大关系的,那么我们把起始记录改为290w看下:

SELECT * FROM test_user LIMIT 2900000,100;   # 3.062s

我们惊讶的发现MySQL在数据量大的情况下分页起点越大,查询速度越慢! 

那么为什么会出现上述这种情况呢?

答案: 因为 limit 2900000,100 的语法实际上是mysql扫描到前2900100条数据,之后丢弃前面的3000000行,这个步骤其实是浪费掉的。

从中我们也能总结出以下两件事情:

  • limit语句的查询时间与起始记录的位置成正比。

  • mysql的limit语句是很方便,但是对记录很多的表并不适合直接使用。

二、MySQL中的limit用法

limit子句可以被用于强制select语句返回指定的记录数,其语法格式如下:

SELECT * FROM 表名 limit m,n;
SELECT * FROM table LIMIT [offset,] rows;

limit接受一个或两个数字参数,参数必须是一个整数常量,如果给定两个参数:

  • 第一个参数指定第一个返回记录行的偏移量
  • 第二个参数指定返回记录行的最大数目

2.1 m代表从m+1条记录行开始检索,n代表取出n条数据。(m可设为0) 

SELECT * FROM 表名 limit 6,5;

上述SQL表示从第7条记录行开始算,取出5条数据 

2.2 值得注意的是,n可以被设置为-1,当n为-1时,表示从m+1行开始检索,直到取出最后一条数据

SELECT * FROM 表名 limit 6,-1;

上述SQL表示取出第6条记录行以后的所有数据

2.3 若只给出m,则表示从第1条记录行开始算一共取出m条

SELECT * FROM 表名 limit 6;

2.4 以年龄倒序后取出前3行

select * from student order by age desc limit 3;

2.5 跳过前3行后再2取行

select * from student order by age desc limit 3,2;

三、深度分页优化策略 

方法一:用主键id或者唯一索引优化

即先找到上次分页的最大id,然后利用id上的索引来查询:

SELECT * FROM test_user WHERE id>1000000 LIMIT 100;  # 0.047秒

使用此优化SQL相比于前面的查询速度已经快了11倍。除了主键ID,也可以利用唯一索引快速定位部分数据,避免全表扫描。例如读取第1000到1019行数据(pk是唯一键),则相对应的优化SQL如下:

SELECT * FROM 表名称 WHERE pk>=1000 ORDER BY pk ASC LIMIT 0,20

原因:索引扫描,速度会很快。

适用场景:如果数据查询出来是按照pk或者id进行排序,并且全部数据没有缺失的话则可以这样优化,否则分页操作会漏数据。

方法二:利用索引覆盖优化

我们都知道,利用了索引查询的语句中如果只包含了那个索引列(也就是索引覆盖),那么这种情况会查询很快。

为什么索引覆盖查询会很快呢?

答案:因为利用索引查找有优化算法,且数据就在查询索引上面,不用再去找相关的数据地址了,这样节省了很多时间。另外Mysql中也有相关的索引缓存,在并发高的时候利用缓存就效果更好了。

在我们的测试表test_user中,id字段是主键,自然就包含了默认的主键索引。现在让我们看看利用覆盖索引的查询效果如何。

这次我们查询第1000001到1000100行的数据(利用覆盖索引,只包含id列):

SELECT id FROM test_user LIMIT 1000000,100;    # 0.843秒

从这个结果中发现查询速度比全表扫描速度还要慢(当然在重复执行这条SQL,多次查询之后速度还是变快了很多,几乎省了一半时间,这是由于缓存的原因), 接着使用explain命令来查看该SQL的执行计划,发现该SQL执行采用的普通索引 idx_user_id

EXPLAIN SELECT id FROM test_user LIMIT 1000000,100;

如果我们把普通索引给删除的话,就会发现执行上述SQL其采用的会是主键索引。那如果不删除普通索引的话,针对这种情况,我们要让上述SQL走主键索引的话,则可以使用order by语句:

SELECT id FROM test_user ORDER BY id ASC LIMIT 1000000,100;  # 0.250秒

那么如果我们也要查询所有列,有两种方法,一种是id>=的形式,另一种就是利用join。

第一种写法: 

SELECT * FROM test_user WHERE ID >= (SELECT id FROM test_user ORDER BY id ASC LIMIT 1000000,1) LIMIT 100;

上述SQL查询时间为0.281秒

第二种写法:

SELECT * FROM (SELECT id FROM test_user ORDER BY id ASC LIMIT 1000000,100) a LEFT JOIN test_user b ON a.id = b.id;

上述SQL查询时间为0.252秒 

方法三:基于索引再排序

其中pageNum表示页码,其取值从0开始;pageSize表示指的是每页多少条数据。

SELECT * FROM 表名称 WHERE id_pk > (pageNum*pageSize) ORDER BY id_pk ASC LIMIT pageSize;

适应场景:

  • 适用于数据量多的情况
  • 最好ORDER BY后的列对象是主键或唯一索引
  • id数据没有缺失,可以作为序号使用
  • 使用ORDER BY操作能利用索引被消除,但结果集是稳定的

原因:

  • 索引扫描,速度会很快
  • 但MySQL的排序操作,只有ASC没有DESC。MySQL中索引存储的排序方式是ASC的,没有DESC的索引。这就能够理解为啥order by 默认是按照ASC来排序的了吧

方法四:基于索引使用prepare

PREPARE预编译一个SQL语句,并为其分配一个名称 stmt_name,以便以后引用该语句,预编译好的语句用EXECUTE执行。 

PREPARE stmt_name FROM 'SELECT * FROM test_user WHERE id > ? ORDER BY id ASC LIMIT ?';
SET @a = 1000000;
SET @b = 100;
EXECUTE stmt_name USING @a, @b;;

上述SQL查询时间为0.047秒。 

对于定义好的PREPARE预编译语句,我们可以使用下述命令来释放该预编译语句:

DEALLOCATE PREPARE stmt_name;

原因:

  • 索引扫描,速度会很快.
  • prepare语句又比一般的查询语句快一点。

方法五:利用"子查询+索引"快速定位数据 

其中page表示页码,其取值从0开始;pagesize表示指的是每页多少条数据。 

SELECT * FROM your_table WHERE id <= (SELECT id FROM your_table ORDER BY id DESC LIMIT ($page-1)*$pagesize ORDER BY id DESC LIMIT $pagesize);

方法六:利用复合索引进行优化

假设数据表 collect ( id, title ,info ,vtype) 就这4个字段,其中id是主键自增,title用定长,info用text, vtype是tinyint,vtype是一个普通索引。

现在往里面填充数据,填充10万条记录,数据库表占用硬1.6G。

select id,title from collect limit 1000,10;

执行上述SQL速度很快,基本上0.01秒就OK。

select id,title from collect limit 90000,10;

然后再执行上述SQL,就发现非常慢,基本上平均8~9秒完成。

这个时候如果我们执行下述,我们会发现速度又变的很快,0.04秒就OK。

select id from collect order by id limit 90000,10;

那么这个现象的原因是什么?

答案:因为用了id主键做索引,  这里实现了索引覆盖,当然快。

所以如果想一起查询其它列的话,可以按照索引覆盖进行优化,具体如下:

select id,title from collect where id >= (select id from collect order by id limit 90000,1) limit 10;

再看下面的语句,带上where 条件:

select id from collect where vtype=1 order by id limit 90000,10; 

可以发现这个速度上也是很慢的,用了8~9秒!

这里有一个疑惑:vtype 做了索引了啊?怎么会慢呢?

vtype做了索引是不错,如果直接对vtype进行过滤:

select id from collect where vtype=1 limit 1000,10;

可以看到速度还是很快的,基本上0.05秒,如果从9万开始,那就是0.05*90=4.5秒的速度了。

其实加了 order by id 就不走索引,这样做还是全表扫描,解决的办法是:复合索引

因此针对下述SQL深度分页优化时可以加一个search_index(vtype,id)复合索引:

select id from collect where vtype=1 order by id limit 90000,10; 

综上: 

  1. 在进行SQL查询深度分页优化时,如果对于有where条件,又想走索引用limit的,必须设计一个索引,将where放第一位,limit用到的主键放第二位,而且只能select 主键。
  2. 最后根据查询出的主键走一级索引找到对应的数据。
  3. 按这样的逻辑,百万级的limit 在0.0x秒就可以分完,完美解决了分页问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/392319.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式学习笔记——寄存器开发STM32 GPIO口

寄存器开发STM32GPIO口前言认识GPIOGPIO是什么GPIO有什么用GPIO怎么用STM32上GPIO的命名以及数量GPIO口的框图&#xff08;重点&#xff09;输入框图解析三种输入模式GPIO输入时内部器件及其作用1.保护二极管2.上下拉电阻&#xff08;可配置&#xff09;3.施密特触发器4.输入数…

什么是CatGPT-使用效果如何-

个人使用效果&#xff0c;评分优&#xff0c;足以满足教学和填表。程序媛借助CatGPT&#xff08;ChatGPT更佳&#xff09;&#xff0c;基本上可以秒杀不用此类工具的程序猿&#xff08;男&#xff09;&#xff01;&#xff01;&#xff01;问&#xff1a;为什么使用AIGC能大幅度…

79、ClimateNeRF: Physically-based Neural Rendering for Extreme Climate Synthesis

简介主页物理模拟可以很好地预测天气影响。神经辐射场产生SOTA场景模型。ClimateNeRF 允许我们渲染真实的天气效果&#xff0c;包括雾霾、雪和洪水 &#xff0c;结果可以通过有物理意义的变量来控制&#xff0c;比如水位 &#xff0c;这允许人们可视化气候变化的结果将对他们产…

2022年MathorCup数学建模B题无人仓的搬运机器人调度问题解题全过程文档加程序

2022年第十二届MathorCup高校数学建模 B题 无人仓的搬运机器人调度问题 原题再现 本题考虑在无人仓内的仓库管理问题之一&#xff0c;搬运机器人 AGV 的调度问题。更多的背景介绍请参看附件-背景介绍。对于无人仓来说&#xff0c;仓库的地图模型可以简化为图的数据结构。 仓库…

面试常问【进程间通信】最详细解说

文章目录 目录 文章目录 一、进程间有哪几种的通信方式 1.为什么需要进程间通信? 2.进程通信的几种方式 3.进程间通信的目的 二、管道 1.匿名管道 2.命名管道 3.管道总结 三、消息队列 四、共享内存 1.共享内存的原理 2.共享内存的接口 1.创建共享内存 2.将共享内存附加到进程…

HBase---Hbase安装(单机版)

Hbase安装单机版 文章目录Hbase安装单机版Master/Slave架构安装步骤配置Hbase1.上传压缩包解压更名修改hbase-env.sh修改hbase-site.xml配置HBase环境变量配置Zookeeper复制配置文件修改zoo.cfg配置文件修改myid配置Zookeeper环境变量刷信息配置文件启动hbase步骤hbase shellMa…

BS系统中的安全方案(SSO和Oauth2认证,数据加密)

摘要用户用浏览器打开网站&#xff0c;DNS会根据域名找到相应的服务器IP给到浏览器&#xff0c;仅接着用户的浏览器会与服务器建立连接&#xff0c;通过网路上的各个设备(交换机、路由器、基站、光纤等)&#xff0c;将服务器上的数据发送到用户的电脑上&#xff0c;在浏览器里呈…

运输层概述及web请求

运输层 运输层概述 运输层向高层用户屏蔽了下面网络核心的细节&#xff08;如网络拓扑、所采用的路由选择协议等&#xff09;它使应用进程看见的就好像是在两个运输层实体之间有一条端到端的逻辑通信信道&#xff1b; 根据需求不同&#xff0c;运输层提供两种运输协议 面向连…

buffer它到底做了个啥,源码级分析linux内核的文件系统的缓冲区

最近一直在学习linux内核源码&#xff0c;总结一下 https://github.com/xiaozhang8tuo/linux-kernel-0.11 自己整理过的带注释的源码。 为什么要有buffer ​ 高速缓冲区是文件系统访问块设备中数据的必经要道(PS:如果所有程序结果都不落盘&#xff0c;只是int a, a直接在主存…

TryHackMe-Debug(ez php反序列化)

Debug Linux机器CTF&#xff01;您将了解枚举&#xff0c;查找隐藏的密码文件以及如何利用php反序列化&#xff01; 端口扫描 循例&#xff0c;nmap Web枚举 进到web是apache默认页面&#xff0c;直接开扫 由于题目告诉我们涉及php反序列化&#xff0c;那直接找php文件来看&…

Linux学习第十四节-shell脚本

1.Shell概述 Shell连接了用户和Linux内核&#xff0c;他可以解释用户输入的命令传输给内核&#xff0c;让用户可以更加方便的使用Linux系统&#xff1b; Shell本身并不是内核的一部分&#xff0c;他只是站在内核的基础上编写一个应用程序&#xff1b; Shell具备编程的能力&a…

C++回顾(八)—— 继承

8.1 继承的概念 继承是类与类之间的关系&#xff0c;是一个很简单很直观的概念&#xff0c;与现实世界中的继承类似&#xff0c;例如儿子继承父亲的财产。 继承&#xff08;Inheritance&#xff09;可以理解为一个类从另一个类获取成员变量和成员函数的过程。例如类 B 继承于类…

jconsole远程linux下的tomcat

修改Tomcat的配置 进去 Tomcat 安装目录下的 bin 目录&#xff0c;编辑 catalina.sh vi catalina.sh定位到 ----- Execute The Requested Command ----------------------------------------- vi 编辑模式下&#xff0c;点击 Esc&#xff0c;输入 / &#xff0c;然后粘贴 -…

什么是Netty

一&#xff0e;Netty介绍 1.什么是netty Netty 是由 JBOSS 提供的一个 Java 开源框架。Netty 提供异步的、基于事件驱动的网络应用程序框架&#xff0c;用以快速开发高性能、高可靠性的网络 IO 程序,是目前最流行的 NIO 框架&#xff0c;Netty 在互联网领域、大数据分布式计算…

自动驾驶介绍系列 ———— 看门狗

文章目录硬件看门狗软件看门狗差异分析延申窗口看门狗硬件看门狗 硬件看门狗的本质上是一个定时器电路。通常存在一个输入&#xff0c;输入到MCU的RST端。在正常工作状态下&#xff0c;MCU每隔固定时间间隔会输出一个信号给RST端&#xff0c;实现对看门狗端清零。如果在指定的时…

RK3568驱动OV13850摄像头模组调试过程

摄像头介绍品牌&#xff1a;Omnivision型号&#xff1a;CMK-OV13850接口&#xff1a;MIPI像素&#xff1a;1320WOV13850彩色图像传感器是一款低电压、高性能1/3.06英寸1320万像素CMOS图像传感器&#xff0c;使用OmniBSI?技术提供了单-1320万像素&#xff08;42243136)摄像头的…

C++20 协程体验

1 介绍协程是比线程更加轻量级并发编程方式&#xff0c;CPU资源在用户态进行切换,CPU切换信息在用户态保存。协程完成异步的调用流程&#xff0c;并对用户展示出同步的使用方式。协程的调度由应用层决定&#xff0c;所以不同的实现会有不同的调度方式&#xff0c;调度策略比较灵…

麻雀算法SSA优化LSTM长短期记忆网络实现分类算法

1、摘要 本文主要讲解&#xff1a;麻雀算法SSA优化LSTM长短期记忆网络实现分类算法 主要思路&#xff1a; 准备一份分类数据&#xff0c;数据介绍在第二章准备好麻雀算法SSA&#xff0c;要用随机数据跑起来用lstm把分类数据跑起来将lstm的超参数交给SSA去优化优化完的最优参数…

Python可变对象与不可变对象的浅拷贝与深拷贝

前言 本文主要介绍了python中容易面临的考试点和犯错点&#xff0c;即浅拷贝与深拷贝 首先&#xff0c;针对Python中的可变对象来说&#xff0c;例如列表&#xff0c;我们可以通过以下方式进行浅拷贝和深拷贝操作&#xff1a; import copya [1, 2, 3, 4, [a, b]]b a …

小众实用!5款不为人知的Windows软件,让你工作更轻松

分享5款冷门但值得下载的Windows软件&#xff0c;个个都是实用&#xff0c;你可能一个都没见过&#xff0c;但是 我觉得你用过之后可能就再也离不开了。 1.键盘可视化——Keyviz Keyviz是一款免费开源的小工具&#xff0c;它的作用是可以实时展示键盘的操作&#xff0c;就可以…