MySQL 查询性能优化

优质博文:IT-BLOG-CN​

在这里插入图片描述

如果把查询看作是一个任务,那么它由一些列子任务组成,每个子任务都会消耗一定的时间。如果要优化查询,实际上要优化其子任务,要么消除其中一些子任务,要么减少子任务的执行次数。通常来说,查询的生命周期大致可以按照顺序来看:从客户端到服务器,然后在服务器上进行解析,生成执行计划,执行,并返回结果给客户端。其中“执行”可以认为是整个生命周期中最重要的阶段,其中包括大量为了检索数据到存储引擎的调用以及调用后的数据处理,包括排序、分组等。上述操作会在网络、CPU计算、生成统计信息和执行计划、锁等待(互斥等待)等操作上花费时间,尤其是向底层存储引擎检索数据的调用操作。根据存储引擎的不同,可能还会产生大量的上下文切换以及系统调用。

一、是否请求了不需要的数据

查询性能低下最基本的原因是访问的数据太多。大部分性能低下的查询都可以通过减少访问的数据量的方式进行优化。对于低效查询可以通过如下两个步骤来分析总是有效:
【1】确定应用程序是否在检索大量超过需要的数据。意味着访问了太多的行或者太多的列。
【2】确定 MySQL 服务器是否在分析大量超过需要的数据行。

有些查询会请求超过实际需要的数据,然后这些多余的数据会被应用程序丢弃。这会给 MySQL 服务器带来额外的负担,并增加网络开销【应用服务器和数据库不再同一台服务器上】另外也会消耗应用服务器的 CPU和内存资源。通常企业不允许使用 SELECT * 语句进行查询。

二、是否扫描了额外的记录

在确定查询只返回需要的数据以后,接下来应该查看查询是否扫描了过多的数据。对于 MySQL,最简单的衡量查询开销的三个指标是响应时间、扫描的行数、返回的行数:这三个指标都会记录到慢日志【SHOW VARIABLES LIKE “%slow%”;】中。
【1】响应时间: 服务时间和排队时间之和,服务时间是指数据库处理这个查询真正花费的时间。排队时间是指服务器因为等待某些资源而没有真正执行查询的时间(等待I/O操作或锁,等等)。遗憾的是无法将响应时间细分到上面这些部分。
【2】扫描的行数和返回的行数: 分析查询时,查看该查询扫描的行数是非常有帮助的。但并不是所有的行的访问代价都是相同的。较短的行访问速度快,内存中的行也比磁盘中的行的访问速度要快很多。理想情况下扫描的行数和返回的行数应该是相同的。但这种情况并不多。例如在做一个关联查询时,服务器必须要扫描多行才能生成结果集中的一行。扫描的行数对返回的行数的比率通常很小,以便在1:1和10:1之间,不过有时候这个值也可能非常非常大。
【3】扫描的行数和访问类型: 在评估查询开销的时候,需要考虑一下从表中找到某一行数据的成本。MySQL 有好几种查询方式可以查找并返回一行结果。有些访问方式可能需要扫描多行才能返回一行结果,也有些访问方式可能无需扫描就能返回结果。在EXPLAN 语句中的 type 列反映了访问类型。从全表扫描、索引扫描、范围扫描、唯一索引查询、常数引用等。速度从慢到快,扫描的行数也是从多到少。如果查询没有办法找到合适的访问类型,那么解决的最好办法就是添加一个合适的索引。索引让 MySQL 以最高效、扫描行数最少的方式找到需要的记录。
【4】如果发现查询需要扫描大量的数据但只返回少数行: 通常可以使用如下技巧去优化它:①、使用索引覆盖扫描,把所有需要的列都放到索引中,这样存储引擎无需回表获取对应行就可以返回结果了。②、改变表结构。例如使用单独的汇总表。③、重写这个复杂的查询,让 MySQL 优化器能够以更优化的方式执行这个查询。

三、一个复杂查询 OR 多个简单查询

有时候,可以将查询转换一种写法让其返回一样的结果,但是性能更好。但也可以通过修改应用代码,用另一种方式完成查询,达到最后的目的。

设计查询的时候需要考虑一个重要问题,是否需要将一个复杂的查询分成多个简单的查询。在传统的实现中,总是强调需要数据库层完成尽可能多的工作,这样做逻辑在于以前总是认为网络通信、查询解析和优化是一件代价很高的事情。对于MySQL 并不适用,MySQL 从设计上让连接和断开连接都是轻量级, 在返回一个小的查询结果很高效。现在的网络速度比以前也快很多,无论是宽带还是延迟。即使一个通用的服务器上,也能够运行每秒超过10万的查询。

四、切分查询

有时候对于一个大查询我们需要 “分而治之” 将大查询切分成小查询,每个查询功能完全一样,只是完成一小部分,每次只返回一小部分查询结果。删除旧的数据就是一个很好的例子。定期地清除大量数据时,如果用一个大的语句一次性完成的话,则可能需要一次性锁住很多数据、占满整个事务日志,耗尽系统资源、阻塞很多小的但重要的查询。将一个大的DELETE 切分成多个较小的查询可以尽可能小地影响 MySQL 性能,同时还可以减少 MySQL 的复制延迟。一秒删除一万行数据一般来说是一个比较高效而且对服务器影响也比较小的做法。如果每次删除数据后,都暂停一会儿再做下一次删除,这样也可以将服务器上原本一次性的压力分散到一个很长的时间段中,就可以大大降低对服务器的影响,还可以大大降低删除时锁的持有时间。

五、分解关联查询

很多高性能的应用都会对关联查询进行分解。可以对每一个表进行一次单表查询,然后将结果在应用程序中进行关联。如下:

SELECT * FROM teacher t
JOIN student s ON t.id = s.t_id
JOIN class c ON t.id = c.t_id
WHERE t.name='Li';
--拆分后
SELECT * FROM teacher t WHERE t.name='Li';
SELECT * FROM student s WHERE s.id = 12;
SELECT * FROM class c WHERE c.id IN (13,45,65);

用分解关联查询的方式重构查询有如下的优势:
【1】让缓存的效果更高,许多应用程序可以方便地缓存单表查询对应的结果对象。例如,上面的 teacher 已经被缓存了,那么应用就跳过了第一个查询,再例如,应用程序中已经缓存了 ID 为 12、45 的内容,那么第三个查询的 IN() 中就可以少几个 ID。另外,对于MySQL 的查询缓存来说,如果关联中某个表发生了变化,那么就无法使用查询缓存了,而拆分后,如果某个表很少改变,那么基于该表的查询就可以重复利用查询缓存结果了。
【2】将查询分解后,执行单个查询就可以减少锁的竞争。
【3】在应用层做关联,可以更容易对数据库进行拆分,更容易做到高性能和可扩展。
【4】查询本身效率也可能有所提升。这个例子中,使用 IN() 代替关联查询,可以让 MySQL 按照ID 顺序进行查询,这可能比随机的关联要更高效。
【5】可以减少冗余记录的查询。在应用层做关联查询,意味着对于某条记录应用只需要查询一次,而在数据库中做关联查询,则可能需要重复地访问一部分数据。这样的重构还可能会减少网络和内存的消耗。
【6】这样做相当于在应用中实现了哈希关联,而不是使用 MySQL 的嵌套循环关联。某些场景哈希关联效率要高很多。

六、UNION 的限制

MySQL 无法将外层限制条件延续到内层,这使得原本可以返回部分结果的条件无法应用到内部查询的优化上。如果希望 UNION 的各个子句根据 LIMIT 只取部分结果集,或者希望能够先排好序再合并结果集的话,就需要在 UNION 的各个子句中分别使用这些子句。例如,想将两个子查询结果联合起来,然后再取前20条记录,那么MySQL 会将两个表都存放到同一个临时表中,然后再取出前20行记录:

--UNION 操作符选取不同的值。如果允许重复的值,请使用 UNION ALL
(SELECT first_name,last_name FROM people_A ORDER BY last_name)
UNION ALL
(SELECT first_name,last_name FROM people_B ORDER BY last_name)
LIMIT 20;

这条查询将会把 people_A 中的所有记录和 people_B 的所有记录放在一个临时表中,然后再从临时表中取出前20条。可以通过在 UNION 的两个子查询中分别加上一个 LIMIT 20来减少临时表中的数据:

(SELECT first_name,last_name FROM people_A ORDER BY last_name LIMIT 20)
UNION ALL
(SELECT first_name,last_name FROM people_B ORDER BY last_name LIMIT 20)
LIMIT 20;

现在中间的临时表只会包含40条记录,除了性能考虑之外,在这里还需要注意一点,从临时表中取出数据的顺序并不是一定的,所以如果想获得正确的顺序,还需要加上一个全局的 ORDER BY 和 LIMIT 操作。

MySQL 总是通过创建并填充临时表的方式来执行 UNION 查询。除非确定需要服务器消除重复的行,否则就一定要使用 UNION ALL,如果没有 ALL 关键字,MySQL 会给临时表加上 DISTINCT 选项,这会导致给整个临时表做唯一性检查。代价非常高。就是有 ALL 关键字,MySQL 仍然会使用临时表存储结果。事实上,MySQL 总是把结果放入临时表,然后再读出来,再返回给客户端。

七、优化 COUNT() 查询

COUNT() 可以统计某个列值的数量,也可以统计行数。在统计列值时要求列值是非空的(不统计NULL)。如果在COUNT() 的括号中制定了列或者表达式,则统计的就是这个表达式有值的结果数。COUNT()的另一个作用是统计行数,当MySQL确认括号内的表达式值不可能为空的时候,实际上就是在统计行数。最简单的就是当我们使用 COUNT(*) 的时候,这种情况它会忽略所有的列直接统计所有的行数。

MyISAM 的 COUNT() 函数总是非常快,前提是没有任何 WHERE 条件。因为无需实际计算表的行数。MySQL 可以利用存储引擎的特性直接获取这个值。如果 MySQL 知道某个列 col 不可能为 NULL 值,那么内部会将 COUNT(col) 转换成COUNT(*)。

【简单优化】 有时候可以使用 MyISAM 在 COUNT(*) 全表非常快的这个特性,来加速一些特定条件的 COUNT() 查询。比如:

SELECT COUNT(*) FROM  city WHERE ID>5;

通过 SHOW STATUS 的结果可以看到该查询需要扫描 5000行数据。如果将条件反转,先查找ID小于等于5的城市,然后用总城市减就能获得同样的结果,却可以将扫描数减少到5行以内。

--ID 是索引,所以会去前5行数据
SELECT (SELECT COUNT(*) FROM city)-COUNT(*) FROM  city WHERE ID<=5;

通常来说,COUNT() 都需要扫描大量的行才能获得精准的结果,因为是很难优化的。在MySQL 层面还能做的就只有索引覆盖扫描了。如果还不够,就需要考虑修改应用的架构,可以增加汇总表,或者增加类似 memcached 缓存系统。

八、优化 LIMIT 分页

在进行分页操作的时候,通常会使用 LIMIT 加上偏移量的办法实现,同时加上合适的 ORDER BY 子句。如果有对应的索引效率会不错,否则,MySQL 要做大量的文件排序操作。有一个问题,当偏移量非常大的时候,例如 LIMIT 10 000,20 这样的查询,这是需要查询10 020条记录然后只返回 20条,前面的10 000条记录都将被抛弃,这样代价太高。优化此类分页查询的最简单办法就是尽可能地使用覆盖索引扫描,而不是查询所有列。对于偏移量大的时候,这样做的效率会提升非常大。例如:

SELECT id,description FROM tab ORDER BY title LIMIT 10000,20;
--使用覆盖索引优化后的语句如下:
SELECT f.id,f.description FROM tab f 
INNER JOIN (SELECT id FROM tab ORDER BY title LIMIT 10000,20) t
USING(id);

这里的 “延迟关联” 将大大提升查询效率,它让 MySQL 扫描尽量少的页面,获取需要访问的记录后再根据关联列回原表查询需要的所有列。这个技术也可以用于优化关联查询中的 LIMIT 子句。

九、排序优化

排序是一个成本很高的操作,所以从性能角度考虑,应尽可能避免排序或者尽可能避免对大量数据进行排序。如果数据量小于 “排序缓冲区” 则在内存中排序,如果数据量大于 “排序缓冲区” 则使用磁盘进行排序 。MySQL 将这一过程统称为 “文件排序:filesort”(前提没有使用索引)。 MySQL 使用内存进行 “快速排序” 操作。如果内存不够排序,那么 MySQL 会先将数据分块,对每个队列的块使用 “快速排序” 进行排序,并将各个块的排序结果存放在磁盘上,然后将各个排好序的块进行合并(merger),最后返回排序结果。

【1】两次传输排序(旧版本使用): 读取行指针和需要排序的字段,对其进行排序,然后再根据排序结果读取所需要的数据行。需要进行两次传输,既需要从数据表中读取两次数据。第二次读取数据的时候,因为是读取排序列进行排序后的所有记录,这会产生大量的随机 I/O,所以两次数据传输的成本非常高。
【2】单次传输排序(新版本使用): 先读取排序所需要的列,然后再根据给定的列进行排序,最后直接返回排序结果。因为不需要从数据表中读取两次数据,对于I/O 密集型的应用,这样做的效率高了很多。另外,相比两次传输排序,这个算法只需要一次顺序 I/O 读取所有的数据,而无需任何随机 I/O。缺点是,如果需要返回的数据非常多,非常大,会额外占用大量空间,而这些列对排序本身并没有任何作用。很难说那个算法效率高,当查询需要所有列的总长度不操作参数 max_length_for_sort_data 时,MySQL 使用单次传输排序,可以通过调整该参数来影响 MySQL 排序算法的选择。

MySQL 在进行文件排序的时候需要使用的临时存储空间可能会比想象的要大得多。在关联查询需要排序时,会分为两种情况来处理这样的文件排序。如果 ORDER BY 子句中的所有列都来自关联的一个表,那么 MySQL 在关联处理第一个表的时候就进行了文件排序。使用 EXPLAN 查看时,看到 Extra 字段会有 “Using filesort” 。另一种情况是 MySQL 都会先将结果存放在一张临时表中,然后在所有关联都结束后,再进行文件排序。EXPLAN 结果是 “Using temporary;Using filesort”,如果包含 LIMIT 的话,LIMIT 也会在排序之后应用。在 MySQL5.6 之后。当使用 LIMIT 子句时,MySQL 不会对所有结果进行排序,而是根据实际情况,选择抛弃不满足条件的结果,然后进行排序。

十、查询状态

在分析查询性能的时候,对于一个 MySQL 连接来说,可以通过查看它的状态来观察它正在做什么。最简单的方式是 SHOW FULL PROCESSLIST 命令,该命令返回结果中的 Command 列表示当前的状态。在一个查询的生命周期中,状态会变化多次。MySQL 官方手册中对这些状态值的含义有最权威的解释,如下:
【1】Sleep: 线程正在等待客户端发送新的请求;
【2】Query: 线程正在执行查询或将结果发送给客户端;
【3】Locked: 在 MySQL 服务器层,该线程正在等待表锁。InnoDB的行锁并不会体现在线程状态中;
【4】Analyzing and statistics: 线程正在收集存储引擎的统计信息,并生成查询的执行计划。
【5】Copying to tmp table [on disk]: 线程正在执行查询,将结果都复制到一个临时表,这种状态一般要么再过 GROUP BY,要么是文件排序操作,或者是 UNION 操作。“on disk” 标记,表示 MySQL 正在讲一个内存临时表放到磁盘上。
【6】Sorting result: 线程正在对结果进行排序;
【7】Sending data: 表示多种情况,线程可能在多种状态之间传送数据。

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

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

相关文章

【prometheus-operator】k8s监控redis

1、准备exporter https://github.com/oliver006/redis_exporter oliver006-redis_exporter-amd64.tar # 安装镜像 docker load -i oliver006-redis_exporter-amd64.tar # 上传镜像 docker tag oliver006/redis_exporter ip/monitor/redis_exporter:latest docker push ip/mo…

DevEco Profiler性能调优工具简介

一、概述 应用或服务运行期间可能出现响应速度慢、动画播放不流畅、列表拖动卡顿、应用崩溃或耗电量过高、发烫、交互延迟等现象,这些现象表明应用或服务可能存在性能问题。造成性能问题的原因可能是业务逻辑、应用代码对系统API的误用、对ArkTS对象的不合理持有导致内存泄露…

记录开发STM32遇到的卡死问题-串口

背景&#xff1a;以STM32作为主控&#xff0c;广州大彩显示屏显示&#xff0c;主控实时采集数据&#xff0c;串口波特率115200.设置收发频率为50Hz&#xff0c;即单片机每秒发送50帧数据&#xff0c;每秒接收50帧数据&#xff0c;每帧数据大概14字节。 问题&#xff1a;系统长…

部署prometheus 监控k8s集群

目录 1、主机清单 2、拉取镜像 3、服务安装 4、安装prometheus-operator 5、查看custom metrics api 6、获取prometheus端口 7、将 alertmanager-main 、grafana、prometheus-k8s的端口暴露出来 8、再次查看prometheus端口 9、浏览器访问IP&#xff1a;31940 部署k8集群…

隐私计算实训营学习三:隐私计算框架的架构和技术要点

文章目录 一、隐语架构二、产品层三、算法层3.1 PSI与PIR3.2 Data Analysis-SCQL3.3 Federated Learning 四、计算层4.1 混合调度编译-RayFed4.2 密态引擎4.3 密码原语YACL 五、资源管理层六、互联互通七、跨域管控 一、隐语架构 1、完备性&#xff1a;支持多种技术&#xff0…

基于Springboot的牙科就诊管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的牙科就诊管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍: 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c…

RocketMq 顺序消费、分区消息、延迟发送消息、Topic、tag分类 实战 (消费者) (三)

消费端配置 如下所示&#xff1a;是消费者的配置类&#xff0c;有以下几点需要注意的地方 1、是TargetMessageListener这个监听类&#xff08;下文会把这个监听类的具体代码贴出来&#xff09;&#xff0c;需要把这个监听类订阅。 2、rocketMqDcProperties.getTargetProperties…

【机器学习】k近邻(k-nearest neighbor )算法

文章目录 0. 前言1. 算法原理1.1 距离度量1.2 参数k的选择 2. 优缺点及适用场景3. 改进和扩展4. 案例5. 总结 0. 前言 k近邻&#xff08;k-nearest neighbors&#xff0c;KNN&#xff09;算法是一种基本的监督学习算法&#xff0c;用于分类和回归问题。k值的选择、距离度量及分…

Linux中Oracle数据库启动顺序

首先使用oracle用户登录Linux&#xff0c;用lsnrctl status查看监听状态 1、&#xff1a;进入sqlplus $ sqlplus /nolog SQL> 2&#xff1a;使用sysdab角色登录sqlplus SQL> conn /as sysdba 3&#xff1a;启动数据库 SQL> startup 4&#xff1a;打开Oracle监听 …

微信小程序 - picker-viewer实现省市选择器

简介 本文会基于微信小程序picker viewer组件实现省市选择器的功能。 实现效果 实现代码 布局 <picker-view value"{{value}}" bindchange"bindChange" indicator-style"height: 50px;" style"width: 100%; height: 300px;" &…

OCR研究背景及相关论文分享

光学字符识别&#xff08;Optical Character Recognition&#xff0c;OCR&#xff09;是指使用光学方法将图像中的文字转换为机器可编辑的文本的技术。OCR技术的研究和应用已有数十年的历史&#xff0c;其背景和发展受到多方面因素的影响。 技术需求背景 1.自动化文档处理&am…

【数据分享】2012-2023年全球范围逐年NPP/VIIRS夜间灯光数据

夜间灯光数据是我们在各项研究中经常使用的数据&#xff01;本次我们给大家分享的是2012-2023年全球范围的逐年的NPP/VIIRS夜间灯光数据&#xff0c;数据格式为栅格格式(.tif)。该数据来自于NCEI国家环境信息中心&#xff0c;近期该网站更新了2023年的夜间灯光数据&#xff0c;…

电脑如何关闭自启动应用?cmd一招解决问题

很多小伙伴说电脑刚开机就卡的和定格动画似的&#xff0c;cmd一招解决问题&#xff1a; CtrlR打开cmd,输入&#xff1a;msconfig 进入到这个界面&#xff1a; 点击启动&#xff1a; 打开任务管理器&#xff0c;禁用不要的自启动应用就ok了

Linux——进程间通信管道

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、进程间通信1、进程间通信的目的2、进程间通信发展3、进程间通信分类 二、管道1、什么是管…

tcp 协议详解

什么是 TCP 协议 TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制。TCP 是一个传输层的协议。 如下图&#xff1a; 我们接下来在讲解 TCP/IP 协议栈的下三层时都会先解决这两个问题&#xff1a; 报头与有效载荷如何…

【Linux】线程控制{fork() / vfork / clone/pthread_join()/pthread_cancel()}

文章目录 1.fork() / vfork / clone2.线程等待2.1pthread_join()2.2pthread_tryjoin_np() 3.pthread_exit()4.pthread_cancel()5.一些线程相关的问题6.pthread_detach()7.pthread_self()8.认识线程标识符&#xff1a;pthread_self()获取线程标识符9.POSIX线程库 1.fork() / vfo…

【CVPR2024】CricaVPR

【CVPR2024】CricaVPR: Cross-image Correlation-aware Representation Learning for Visual Place Recognition 这个论文提出了一种具有跨图像相关性的鲁棒全局表示方法用于视觉位置识别&#xff08;VPR&#xff0c;Visual Place Recognition &#xff09;任务&#xff0c;命…

MySQL学习笔记------SQL(1)

关系型数据库&#xff08;RDBMS&#xff09; 建立在关系模型基础上&#xff0c;由多张相互连接的二维表组成的数据库 特点&#xff1a;使用表储存数据&#xff0c;格式统一&#xff0c;便于维护 使用SQL语言操作&#xff0c;标准统一&#xff0c;使用方便 SQL通用语法 SQL…

Java 开篇之 JDK 下载、安装、配置、卸载、运行及乱码处理

JDK 安装和配置 本文内容简介&#xff1a; JDK 简介JDK 正确卸载方式JDK 下载和安装环境变量配置开发过程运行结果及乱码解决 JDK 简介 JDK&#xff08;Java Development Kit&#xff09;是 Java 语言的软件开发工具包&#xff0c;它是 Sun Microsystems&#xff08;现已被 Ora…

【研发日记】C/C++开发避坑秘籍(一)——CAN接收Buffer溢出Bug

文章目录 背景介绍 问题描述 分析排查 解决方案 总结归纳 背景介绍 在一个嵌入式软件项目中&#xff0c;有一段使用C语言写的嵌入式代码&#xff0c;功能是把CAN总线上的几帧报文接收进来&#xff0c;并解析出数据。示例如下&#xff1a; 乍一看感觉挺简单&#xff0c;想着…