MySQL 的执行原理(五)

news2024/12/28 21:26:12

5.6 再深入查询优化

5.6.1. 全局考虑性能优化

5.6.3.1. 为什么查询速度会慢

在尝试编写快速的查询之前,需要清楚一点,真正重要是响应时间。如果把查询看作是一个任务,那么它由一系列子任务组成,每个子任务都会消耗一定的时间。如果要优化查询,实际上要优化其子任务,要么消除其中一些子任务,要么减少子任务的执行次数,要么让子任务运行得更快。

MySQL 查询的生命周期大致可以按照顺序来看:从客户端,到服务器,然后在服务器上进行解析,生成执行计划,执行,并返回结果给客户端。其中“执行”可以认为是整个生命周期中最重要的阶段,这其中包括了大量为了检索数据到存储引擎的调用以及调用后的数据处理,包括排序、分组等。

在完成这些任务的时候,查询需要在不同的地方花费时间,包括网络,CPU计算,生成统计信息和执行计划、锁等待(互斥等待)等操作,尤其是向底层存储引擎检索数据的调用操作,这些调用需要在内存操作,CPU 操作和内存不足时导致的 IO 操作上消耗时间。根据存储引擎不同,可能还会产生大量的上下文切换以及系统调用。
优化查询的目的就是减少和消除这些操作所花费的时间。

5.6.3.2. 查询执行的流程再回顾

当希望 MySQL 能够以更高的性能运行查询时,最好的办法就是弄清楚MySQL 是如何优化和执行查询的。一旦理解这一点,很多查询优化工作实际上就是遵循一些原则让优化器能够按照预想的合理的方式运行。

换句话说,是时候回头看看我们前面讨论的内容了:MySQL 执行一个查询的过程。根据下图,我们可以看到当向 MySQL 发送一个请求的时候,MySQL 到底做了些什么:
在这里插入图片描述
1.客户端发送一条查询给服务器。
2.服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段。
3.服务器端进行 SQL 解析、预处理,再由优化器生成对应的执行计划。
4. MySQL 根据优化器生成的执行计划,调用存储引擎的 API 来执行查询。
5.将结果返回给客户端。

上面的每一步都比想象的复杂,查询优化器固然是其中特别复杂也特别难理解的部分。但是其他部分就对查询的性能毫无影响?

5.6.3.3. MySQL 客户端/服务器通信协议

一般来说,不需要去理解 MySQL 通信协议的内部实现细节,只需要大致理解通信协议是如何工作的。MySQL 客户端和服务器之间的通信协议是“半双工”的,这意味着,在任何一个时刻,要么是由服务器向客户端发送数据,要么是由客户端向服务器发送数据,这两个动作不能同时发生。所以,我们无法也无须将一个消息切成小块独立来发送。

这种协议让 MySQL 通信简单快速,但是也从很多地方限制了 MySQL。一个明显的限制是,这意味着没法进行流量控制。一旦一端开始发生消息,另一端要接收完整个消息才能响应它。这就像来回抛球的游戏﹔在任何时刻,只有一个人能控制球,而且只有控制球的人才能将球抛回去(发送消息)。

客户端用一个单独的数据包将查询传给服务器。这也是为什么当查询的语句很长的时候,参数 max_allowed_packet 就特别重要了。一旦客户端发送了请求,它能做的事情就只是等待结果了。

相反的,一般服务器响应给用户的数据通常很多,由多个数据包组成。当服务器开始响应客户端请求时,客户端必须完整地接收整个返回结果,而不能简单地只取前面几条结果,然后让服务器停止发送数据。这种情况下,客户端若接收完整的结果,然后取前面几条需要的结果,或者接收完几条结果后就“粗暴”地断开连接,都不是好主意。这也是在必要的时候一定要在查询中加上 LIMIT 限制的原因。

换一种方式解释这种行为:当客户端从服务器取数据时,看起来是一个拉数据的过程,但实际上是 MySQL 在向客户端推送数据的过程。客户端不断地接收从服务器推送的数据,客户端也没法让服务器停下来。

多数连接 MySQL 的库函数都可以获得全部结果集并缓存到内存里,还可以逐行获取需要的数据。默认一般是获得全部结果集并缓存到内存中。MySQL 通常需要等所有的数据都已经发送给客户端才能释放这条查询所占用的资源,所以接收全部结果并缓存通常可以减少服务器的压力,让查询能够早点结束、早点释放相应的资源。

当使用库函数从 MySQL 获取数据时,其结果看起来都像是从 MySQL 服务器获取数据,而实际上都是从这个库函数的缓存获取数据。多数情况下这没什么问题,但是如果需要返回一个很大的结果集的时候,这样做并不好,因为库函数会花很多时间和内存来存储所有的结果集。

对于 Java 程序来说,很有可能发生 OOM,所以 MySQL 的 JDBC 里提供了setFetchSize() 之类的功能,来解决这个问题:

1、当 statement 设置以下属性时,采用的是流数据接收方式,每次只从服务器接收部份数据,直到所有数据处理完毕,不会发生 JVM OOM。

setResultSetType(ResultSet.TYPE_FORWARD_ONLY);
setFetchSize(Integer.MIN_VALUE)

2、调用 statement 的 enableStreamingResults 方法,实际上
enableStreamingResults 方法内部封装的就是第 1 种方式。

3、设置连接属性 useCursorFetch=true (5.0 版驱动开始支持),statement 以 TYPE_FORWARD_ONLY 打开,再设置 fetch size 参数,表示采用服务器端游标,每次从服务器取 fetch_size 条数据。

比如:
con = DriverManager.getConnection(url);
ps = (PreparedStatement)
con.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
ps.setFetchSize(Integer.MIN_VALUE);
ps.setFetchDirection(ResultSet.FETCH_REVERSE);
rs = ps.executeQuery();
while (rs.next()) {……实际的业务处理}
5.6.3.4. 查询状态

对于一个 MySQL 连接,或者说一个线程,任何时刻都有一个状态,该状态表示了 MySQL 当前正在做什么。在一个查询的生命周期中,状态会变化很多次。

5.6.3.5. 查询优化处理

查询的生命周期的下一步是将一个 SQL 转换成一个执行计划,MySQL 再依照这个执行计划和存储引擎进行交互。这包括多个子阶段:解析 SQL、预处理、优化 SQL 执行计划。这个过程中任何错误(例如语法错误)都可能终止查询。在实际执行中,这几部分可能一起执行也可能单独执行。

我们通过前面的学习,可以看到 MySQL 的查询优化器是一个非常复杂的部件,它使用了很多优化策略来生成一个最优的执行计划。优化策略可以简单地分为两种,一种是静态优化,一种是动态优化。静态优化可以直接对解析树进行分析,并完成优化。例如,优化器可以通过一些简单的代数变换将 WHERE 条件转
换成另一种等价形式。静态优化不依赖于特别的数值,如 WHERE 条中带入的一些常数等。静态优化在第一次完成后就一直有效,即使使用不同的参数重复执行查询也不会发生变化。可以认为这是一种“编译时优化”。

相反,动态优化则和查询的上下文有关,也可能和很多其他因素有关,例如WHERE 条件中的取值、索引中条目对应的数据行数等。这需要在每次查询的时候都重新评估,可以认为这是“运行时优化”。
优化器是相当复杂性和智能的。建议大家“不要自以为比优化器更聪明”。
如果没有必要,不要去干扰优化器的工作,让优化器按照它的方式工作。尽量按照优化器的提示去优化我们的表、索引和 SQL 语句,比如写查询,或者重新设计更优的库表结构,或者添加更合适的索引。但是请尽可能的保持 SQL 语句的简洁,SQL 语句变得很复杂的情况下,请相信我,维护会成为一个地狱。而带来的最终的收益微乎其微。

当然,虽然优化器已经很智能了,但是有时候也无法给出最优的结果。有时候你可能比优化器更了解数据,例如,由于应用逻辑使得某些条件总是成立﹔还有时,优化器缺少某种功能特性,如哈希索引﹔再如前面提到的,从优化器的执行成本角度评估出来的最优执行计划,实际运行中可能比其他的执行计划更慢。
如果能够确认优化器给出的不是最佳选择,并且清楚优化背后的原理,那么也可以帮助优化器做进一步的优化。

MySQL 架构由多个层次组成。在服务器层有查询优化器,却没有保存数据和索引的统计信息。统计信息申存储引擎实现,不同的存储引擎可能会存储不同的统计信息(也可以按照不同的格式存储统计信息)。某些引擎,例如 Archive 引擎,则根本就没有存储任何统计信息!
因为服务器层没有任何统计信息,所以 MySQL 查询优化器在生成查询的执行计划时,需要向存储引擎获取相应的统计信息。存储引擎则提供给优化器对应的统计信息,包括:每个表或者索引有多少个页面、每个表的每个索引的基数是多少、数据行和索引长度、索引的分布信息等。优化器根据这些信息来选择一个最优的执行计划。

当出现不理想的 SQL 查询时,我们就需要知道查询优化器是如何工作的,以便有针对性的进行改进,不管是 SQL 语句本身还是表结构相关,比如索引。这个时候请仔细耐心的对慢查询进行分析。

5.6.3.6. 查询执行引擎

在解析和优化阶段,MySQL 将生成查询对应的执行计划,MySQL 的查询执行引擎则根据这个执行计划来完成整个查询。相对于查询优化阶段,查询执行阶段不是那么复杂:MySQL 只是简单地根据执行计划给出的指令逐步执行。

5.6.3.7. 返回结果给客户端

查询执行的最后一个阶段是将结果返回给客户端。即使查询不需要返回结果集给客户端,MySQL 仍然会返回这个查询的一些信息,如该查询影响到的行数。

如果查询可以被缓存,那么 MySQL 在这个阶段也会将结果存放到查询缓存中。
MySQL 将结果集返回客户端是一个增量、逐步返回的过程。一旦服务器开始生成第一条结果时,MySQL 就可以开始向客户端逐步返回结果集了。

这样处理有两个好处﹔服务器端无须存储太多的结果,也就不会因为要返回太多结果而消耗太多内存。另外,这样的处理也让 MySQL 客户端第一时间获得返回的结果。结果集中的每一行都会以一个满足 MySQL 客户端/服务器通信协议的封包发送,再通过 TCP 协议进行传输,在 TCP 传输的过程中,可能对 MySQL的封包进行缓存然后批量传输。

5.6.3.8. 通过 show profile 分析 SQL

通过上面的描述可知,当我们通过应用程序访问 MySQL 服务时,有时候性能不一定全部卡在语句的执行上。当然通过慢查询日志定位那些执行效率较低的SQL 语句时候我们常用的手段,但是:

一、慢查询日志在查询结束以后才记录,在应用反映执行效率出现问题的时候查询未必执行完成;
二、有时候问题的产生不一定是语句的执行,有可能是其他原因导致的。慢查询日志并不能定位问题。

show processlist
这个时候通过 show processlist;查看线程状态非常有用,这可以让我们很快地了解当前 MySQL 在进行的线程,包括线程的状态、是否锁表等,可以实时地查看SQL 的执行情况,同时对一些锁表操作进行优化。在一个繁忙的服务器上,可能会看到大量的不正常的状态,例如 statistics 正占用大量的时间。这通常表示,某个地方有异常了。线程常见的状态有很多,比如:
statistics
The server is calculating statistics to develop a query execution plan. If a thread is in this state for a long time, the server is probably disk-bound performing other work.

服务器正在计算统计信息以研究一个查询执行计划。如果线程长时间处于此状态,则服务器可能是磁盘绑定执行其他工作。

Creating tmp table
The thread is creating a temporary table in memory or on disk. If the table is created in memory but later is converted to an on-disk table, the state during that operation is Copying to tmp table on disk.

该线程正在内存或磁盘上创建临时表。如果表在内存中创建但稍后转换为磁 盘表,则该操作期间的状态将为 Copying to tmp table on disk
Sending data
The thread is reading and processing rows for a SELECT statement, and sending data to the client. Because operations occurring during this state tend to perform large amounts of disk access (reads), it is often the longest-running state over the lifetime of a given query.

线程正在读取和处理 SELECT 语句的行 ,并将数据发送到客户端。由于在此状态期间发生的操作往往会执行大量磁盘访问(读取),因此它通常是给定查询生命周期中运行时间最长的状态。
其余的可以参考:
https://dev.mysql.com/doc/refman/5.7/en/general-thread-states.html
通过 show profile 分析
对于每个线程到底时间花在哪里,可以通过 show profile 来分析。
1、首先检查当前 MySQL 是否支持 profile

select @@have_profiling;

在这里插入图片描述
2、默认 profiling 是关闭的,可以通过 set 语句在 Session 级别开启 profiling:

select @@profiling;
set profiling=1;

3、执行一个 SQL 查询

select count(*) from order_exp;

4、通过 show profiles 语句,看到当前 SQL 的 Query ID

show profiles;

在这里插入图片描述
5、通过 show profile for query 语句能够看到执行过程中线程的每个状态和消耗的时间

show profile for query 1;

在这里插入图片描述
通过仔细检查 show profile for query 的输出,能够发现在执行 COUNT(*)的过程中,时间主要消耗在 Sending data 这个状态上。

6、在获取到最消耗时间的线程状态后,MySQL 支持进一步选择 all、cpu、block io、contextswitch、page faults 等明细类型来查看 MySQL 在使用什么资源上耗费了过高的时间:

show profile all for query 1\G

在这里插入图片描述
能够发现 Sending data 状态下,时间主要消耗在 CPU 上了。
所以show profile能够在做SQL优化时帮助我们了解时间都耗费到哪里去了,同时如果 MySQL 源码感兴趣,还可以通过 show profile source for query 查看 SQL解析执行过程中每个步骤对应的源码的文件、函数名以及具体的源文件行数。

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

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

相关文章

YOLOv8优化策略:轻量级Backbone改进 | VanillaNet极简神经网络模型 | 华为诺亚2023

🚀🚀🚀本文改进:一种极简的神经网络模型 VanillaNet,支持vanillanet_5, vanillanet_6, vanillanet_7, vanillanet_8, vanillanet_9, vanillanet_10, vanillanet_11等版本 🚀🚀🚀YOLOv8改进专栏:http://t.csdnimg.cn/hGhVK 学姐带你学习YOLOv8,从入门到创新,…

大模型的语言能力

NLP作为一个领域为基础模型开辟了道路。虽然这些模型在标准基准测试中占据主导地位,但这些模型目前获得的能力与那些将语言描述为人类交流和思维的复杂系统的能力之间存在明显的差距。针对这一点,我们强调语言变异的全部范围(例如&#xff0c…

【设计模式】创建型设计模式

创建型设计模式 文章目录 创建型设计模式一、概述二、单例模式三、工厂模式3.1 简单工厂模式(静态工厂模式)3.2 工厂方法模式3.3 抽象工厂模式3.3 工厂模式小结 四、原型模式五、建造者模式 一、概述 这些设计模式提供了一种在创建对象的同时隐藏创建逻…

Java入门篇 之 抽象类接口

本篇碎碎念:个人认为压力是一种前进的动力,但是不要有太多压力,不然会使心情烦躁,会控制不住自己的情绪,会在一个临界值爆发,一旦爆发,将迟迟不能消散 今日份励志文案: 努力的背后必有加倍的赏赐…

用cmd看星球大战大电影,c++版本全集星球大战,超长多细节

用cmd看星球大战 最近发现了一个有趣的指令。 是不是感觉很insteresting呢 教程 进入控制面板,点击系统与安全 然后,进入以后,点击启用或关闭 Windows 功能 启用Telnet Client并点击确定 用快捷键winr打开我们的cmd 输入指令 telnet towe…

使用百度翻译API或腾讯翻译API做一个小翻译工具

前言 书到用时方恨少,只能临时抱佛脚。英文pdf看不懂,压根看不懂。正好有百度翻译API和腾讯翻译API,就利用两个API自己写一个简单的翻译工具,充分利用资源,用的也放心。 前期准备 关键肯定是两大厂的翻译API&#x…

Transformer笔记

Transformer encoder-decoder架构 Encoder:将输入序列转换为一个连续向量空间中的表示。Encoder通常是一个循环神经网络(RNN)或者卷积神经网络(CNN),通过对输入序列中的每个元素进行编码,得到…

spass-二元变量相关分析

基础概念 计算相关系数r&#xff1a;利用样本数据计算样本相关系数&#xff0c;样本相关系数反映了两变量间线性相关程度的强弱。相关系数的取值范围界于-1与1之间&#xff0c;即-1≤r≤1 当0<r ≤ 1&#xff0c;表明变量之间存在正相关关系&#xff1b; 当-1 ≤ r…

pythongui实时闹钟

# codinggbk import tkinter as tk from time import strftime# 创建一个主窗口 root tk.Tk() root.title("实时闹钟")# 设置窗口的大小不可变 root.resizable(False, False)# 设置窗口始终保持在最上层 root.attributes(-topmost, True)# 更新时间的函数 def time(…

038、语义分割

之——介绍与数据集 杂谈 语义分割&#xff0c;语义分割(Semantic Segmentation)方法-CSDN博客&#xff1a; 语义分割是计算机视觉领域的一项重要任务&#xff0c;旨在将图像中的每个像素分配到其对应的语义类别中。与物体检测或图像分类不同&#xff0c;语义分割不仅要识别图像…

linux网络——HTTPS加密原理

目录 一.HTTPS概述 二.概念准备 三.为什么要加密 四.常⻅的加密⽅式 1.对称加密 2.⾮对称加密 五.数据摘要&#xff0c;数字签名 六.HTTPS的加密过程探究 1.方案一——只使用对称加密 2.方案二——只使⽤⾮对称加密 3.方案三——双⽅都使⽤⾮对称加密 4.方案四——⾮…

元素水平垂直居中

方法一&#xff1a;定位transform 方法二&#xff1a;flex布局 方法三&#xff1a;定位margin【需要child 元素自身的宽高】 相关HTML代码&#xff1a; <div class"parent"><div class"child"></div> </div> 方法一&#xff1a…

许多网友可能还不知道,升级到Windows 11其实没那么复杂,只要符合几个条件可以了

如果你的Windows 10电脑可以升级Windows 11,现在怎么办?有几种方法可以免费安装新的操作系统。以下是你的选择。 如果你想升级到Windows 11,你可以随时购买一台已经安装了操作系统的新电脑。然而,如果你目前的Windows 10 PC满足所有必要的升级要求,那么在Windows 11免费的…

AIRLOOK与商汤科技强强联合,打造“实景三维与AI大模型”结合的全新盛宴

实景三维中国建设作为数字中国建设的重要内容之一&#xff0c;是一项涉及多方面技术支撑的综合性工程&#xff0c;同时作为AI技术在其中发挥着至关重要的作用&#xff0c;AI大模型的发展也将进一步推动实景三维建模技术的创新和发展。在此背景下&#xff0c;AIRLOOK与商汤科技携…

AUTODL云服务器使用大致步骤(适合本人版)

(一)在官网上创建一个服务器 (二)远程连接指令&#xff1a; 改为&#xff1a; (三)连接后&#xff0c;可在中进行代码运行 输入一些指令 python ......

Backtrader绘图cerebro.plot报错问题的处理

Backtrader绘图cerebro.plot报错问题的处理 1.问题描述 在jupyter 中使用BackTrader &#xff0c;使用绘图功能时&#xff1a; cerebro.plot() 提示错误&#xff1a;ValueError: Axis limits cannot be NaN or Inf 由于backtrader 要求有7列数据&#xff0c;最后一列openint…

Halcon (5):Halcon Solution Guide I basics 导论解析

文章目录 文章专栏前言文章目录翻译文档的说明 结论 文章专栏 Halcon开发 前言 今天开始看Halcon的官方文档。由于市面上的教学主要是以基础的语法&#xff0c;算子简单介绍为主。所以我还是得看官方的文本。别的不多说了。有道词英语词典&#xff0c;启动。 还有就是今天LOL…

Python基础:错误和异常

在Python中的错误可&#xff08;至少&#xff09;被分为两种&#xff1a;语法错误和 异常&#xff0c;均是指在程序中发生的问题和意外情况。Python提供了异常处理机制&#xff0c;使程序能够更容易地应对这些问题。 1. 语法错误&#xff08;Syntax Error&#xff09; 语法错误…

C++算法入门练习——树的带权路径长度

现有一棵n个结点的树&#xff08;结点编号为从0到n-1&#xff0c;根结点为0号结点&#xff09;&#xff0c;每个结点有各自的权值w。 结点的路径长度是指&#xff0c;从根结点到该结点的边数&#xff1b;结点的带权路径长度是指&#xff0c;结点权值乘以结点的路径长度&#x…