mysql中Select执行全过程

执行一条 select 语句,期间发生了什么?

// 在 product 表中,查询 id = 1 的记录
select * from product where id = 1;

MySQL 执行流程是怎样的?

先来一个上帝视角图,下面就是 MySQL 执行一条 SQL 查询语句的流程,也从图中可以看到 MySQL 内部架构里的各个功能模块。(这个图原作者做的太好啦,一目了然!)

image-20240226081125643

可以看到, MySQL 的架构共分为两层:Server 层和存储引擎层

  • Server 层负责建立连接、分析和执行 SQL。MySQL 大多数的核心功能模块都在这实现,主要包括连接器,查询缓存、解析器、预处理器、优化器、执行器等。另外,所有的内置函数(如日期、时间、数学和加密函数等)和所有跨存储引擎的功能(如存储过程、触发器、视图等。)都在 Server 层实现。
  • 存储引擎层负责数据的存储和提取。支持 InnoDB、MyISAM、Memory 等多个存储引擎,不同的存储引擎共用一个 Server 层。现在最常用的存储引擎是 InnoDB,从 MySQL 5.5 版本开始, InnoDB 成为了 MySQL 的默认存储引擎。我们常说的索引数据结构,就是由存储引擎层实现的,不同的存储引擎支持的索引类型也不相同,比如 InnoDB 支持索引类型是 B+树 ,且是默认使用,也就是说在数据表中创建的主键索引和二级索引默认使用的是 B+ 树索引。

好了,现在我们对 Server 层和存储引擎层有了一个简单认识,接下来,就详细说一条 SQL 查询语句的执行流程,依次看看每一个功能模块的作用。

第一步:连接器

在 MySQL 中,连接器(Connection Manager)是负责处理客户端与服务器之间连接的组件。当客户端应用程序尝试连接到 MySQL 服务器时,连接器负责接受连接请求、建立连接,并处理连接的身份验证和权限验证等过程。以下是 MySQL 内部连接器的主要功能和流程:

  1. 接受连接请求: 当客户端应用程序尝试连接到 MySQL 服务器时,连接器首先接受连接请求。连接请求可以是来自 TCP/IP 连接、Unix 套接字连接或其他支持的连接方式。
  2. 建立连接: 连接器根据客户端请求建立连接,包括分配连接资源、为连接分配标识符等操作。
  3. 身份验证: 连接器在建立连接后,会进行客户端身份验证。这包括验证客户端提供的用户名和密码,以确定客户端是否有权限连接到服务器。
  4. 权限验证: 在身份验证成功后,连接器会对客户端进行权限验证。它会检查客户端对数据库、表和其他资源的操作权限,以确保客户端只能执行其具有权限的操作。
  5. 维护连接池: 连接器通常会维护一个连接池,以便在需要时重复使用已建立的连接,从而减少连接建立的开销和资源消耗。
  6. 处理查询请求: 一旦连接建立成功并通过身份验证和权限验证,连接器就会接收客户端发送的 SQL 查询请求,并将其传递给查询处理器进行处理。

详细讲一讲;

对于mysql连接,需要先经过tcp的三次握手,若密码都没有问题,会验证用户权限,然后保存起来,后续该用户在此连接里的任何操作,都会基于连接开始时读到的权限进行权限逻辑的判断。

所以,如果一个用户已经建立了连接,即使管理员中途修改了该用户的权限,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。

一个mysql服务端是支持多个客户端进行连接的,可用show processlist查看,

image-20240226081936531

sleep标识长时间未执行过命令,并且空闲时间是736秒,MySQL 定义了空闲连接的最大空闲时长,由 wait_timeout 参数控制的,默认值是 8 小时(28880秒),如果空闲连接超过了这个时间,连接器就会自动将它断开。

并且mysql也设置有最大连接数:

image-20240226082203388

可以查看,基本都是151个

连接方式:和http一样有长连接和短连接之分

// 短连接
连接 mysql 服务(TCP 三次握手)
执行sql
断开 mysql 服务(TCP 四次挥手)

// 长连接
连接 mysql 服务(TCP 三次握手)
执行sql
执行sql
执行sql
....
断开 mysql 服务(TCP 四次挥手)

通常会使用长连接,但是,使用长连接后可能会占用内存增多,因为 MySQL 在执行查询过程中临时使用内存管理连接对象,这些连接对象资源只有在连接断开时才会释放。如果长连接累计很多,将导致 MySQL 服务占用内存太大,有可能会被系统强制杀掉,这样会发生 MySQL 服务异常重启的现象。

相信以前使用小服务器部署大量客户访问的网站时最清楚不过,数据库老是自动关闭,难受得很;

当时也是有解决方法,先说下我当时使用情况,编写一个重启脚本,每天晚上12点定时关闭服务器,并经过一分钟后重启,可以有效地减少服务器关闭概率;

通常来说。有两种解决方式。

第一种,定期断开长连接。既然断开连接后就会释放连接占用的内存资源,那么我们可以定期断开长连接。

第二种,客户端主动重置连接。MySQL 5.7 版本实现了 mysql_reset_connection() 函数的接口,注意这是接口函数不是命令,那么当客户端执行了一个很大的操作后,在代码里调用 mysql_reset_connection 函数来重置连接,达到释放内存的效果。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。

至此,连接器的工作做完了,简单总结一下:

  • 与客户端进行 TCP 三次握手建立连接;
  • 校验客户端的用户名和密码,如果用户名或密码不对,则会报错;
  • 如果用户名和密码都对了,会读取该用户的权限,然后后面的权限逻辑判断都基于此时读取到的权限;

第二步:查询缓存

Mysql8.0之后已经移除;

连接器得工作完成后,客户端就可以向 MySQL 服务发送 SQL 语句了,MySQL 服务收到 SQL 语句后,就会解析出 SQL 语句的第一个字段,看看是什么类型的语句。

如果 SQL 是查询语句(select 语句),MySQL 就会先去查询缓存( Query Cache )里查找缓存数据,看看之前有没有执行过这一条命令,这个查询缓存是以 key-value 形式保存在内存中的,key 为 SQL 查询语句,value 为 SQL 语句查询的结果。

如果查询的语句命中查询缓存,那么就会直接返回 value 给客户端。如果查询的语句没有命中查询缓存中,那么就要往下继续执行,等执行完后,查询的结果就会被存入查询缓存中。

这么看,查询缓存还挺有用,但是其实查询缓存挺鸡肋的。

对于更新比较频繁的表,查询缓存的命中率很低的,因为只要一个表有更新操作,那么这个表的查询缓存就会被清空。如果刚缓存了一个查询结果很大的数据,还没被使用的时候,刚好这个表有更新操作,查询缓冲就被清空了,相当于缓存了个寂寞。

所以,MySQL 8.0 版本直接将查询缓存删掉了,也就是说 MySQL 8.0 开始,执行一条 SQL 查询语句,不会再走到查询缓存这个阶段了。

对于 MySQL 8.0 之前的版本,如果想关闭查询缓存,我们可以通过将参数 query_cache_type 设置成 DEMAND。

只对于长时间不更新和频繁查询的静态数据有良好的效果,其他时候甚至起到反作用;

第三步:解析SQL

解析器

解析器会做如下两件事情。

第一件事情,词法分析。MySQL 会根据你输入的字符串识别出关键字出来,例如,SQL语句 select username from userinfo,在分析之后,会得到4个Token,其中有2个Keyword,分别为select和from:

关键字非关键字关键字非关键字
selectusernamefromuserinfo

第二件事情,语法分析。根据词法分析的结果,语法解析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法,如果没问题就会构建出 SQL 语法树,这样方便后面模块获取 SQL 类型、表名、字段名、 where 条件等等。

image-20240226084101493

如果我们输入的 SQL 语句语法不对,就会在解析器这个阶段报错。但是不是什么错误都归解析器管,他只负责检查语法和构建语法树,对于数据是否错误或者存在是在之后进行的

第四步:执行 SQL

经过解析器后,接着就要进入执行 SQL 查询语句的流程了,每条SELECT 查询语句流程主要可以分为下面这三个阶段:

  • prepare 阶段,也就是预处理阶段;
  • optimize 阶段,也就是优化阶段;
  • execute 阶段,也就是执行阶段;

即预处理器,优化器,执行器三个阶段

预处理器

我们先来说说预处理阶段做了什么事情。

  • 检查 SQL 查询语句中的表或者字段是否存在;
  • select * 中的 * 符号,扩展为表上的所有列;

我下面这条查询语句,test 这张表是不存在的,这时 MySQL 就会在执行 SQL 查询语句的 prepare 阶段中报错。

mysql> select * from test;
ERROR 1146 (42S02): Table 'mysql.test' doesn't exist

优化器

经过预处理阶段后,还需要为 SQL 查询语句先制定一个执行计划,这个工作交由「优化器」来完成的。

优化器主要负责将 SQL 查询语句的执行方案确定下来,比如在表里面有多个索引的时候,优化器会基于查询成本的考虑,来决定选择使用哪个索引。

当然,我们本次的查询语句(select * from product where id = 1)很简单,就是选择使用主键索引。

要想知道优化器选择了哪个索引,我们可以在查询语句最前面加个 explain 命令,这样就会输出这条 SQL 语句的执行计划,然后执行计划中的 key 就表示执行过程中使用了哪个索引,比如下图的 key 为 PRIMARY 就是使用了主键索引。

image-20240226085355746

当出现多个索引时,优化器会帮忙找效率最高的索引执行;

执行器

经历完优化器后,就确定了执行方案,接下来 MySQL 就真正开始执行语句了,这个工作是由「执行器」完成的。在执行的过程中,执行器就会和存储引擎交互了,交互是以记录为单位的。

接下来,用三种方式执行过程,跟大家说一下执行器和存储引擎的交互过程(PS :为了写好这一部分,特地去看 MySQL 源码,也是第一次看哈哈)。

  • 主键索引查询
  • 全表扫描
  • 索引下推
select * from product where id = 1;
主键索引查询

这条查询语句的查询条件用到了主键索引,而且是等值查询,同时主键 id 是唯一,不会有 id 相同的记录,所以优化器决定选用访问类型为 const 进行查询,也就是使用主键索引查询一条记录,那么执行器与存储引擎的执行流程是这样的:

  • 执行器第一次查询,会调用 read_first_record 函数指针指向的函数,因为优化器选择的访问类型为 const,这个函数指针被指向为 InnoDB 引擎索引查询的接口,把条件 id = 1 交给存储引擎,让存储引擎定位符合条件的第一条记录
  • 存储引擎通过主键索引的 B+ 树结构定位到 id = 1的第一条记录,如果记录是不存在的,就会向执行器上报记录找不到的错误,然后查询结束。如果记录是存在的,就会将记录返回给执行器;
  • 执行器从存储引擎读到记录后,接着判断记录是否符合查询条件,如果符合则发送给客户端,如果不符合则跳过该记录。
  • 执行器查询的过程是一个 while 循环,所以还会再查一次,但是这次因为不是第一次查询了,所以会调用 read_record 函数指针指向的函数,因为优化器选择的访问类型为 const,这个函数指针被指向为一个永远返回 - 1 的函数,所以当调用该函数的时候,执行器就退出循环,也就是结束查询了。

const类型就是常量类型,即结果为常量,只用查询一次就结束了;

  1. const(常数访问): 表示查询条件是一个常数,优化器可以确定查询结果是一个确定的常数值,因此直接返回该常数值而无需执行实际的查询操作(优化器会使用主键索引直接定位到符合条件的数据行,而不必扫描整个表或者使用其他索引。这样可以在常数时间内获取到查询结果,而不必执行实际的查询操作。)const 访问类型并不意味着完全不执行查询操作,而是表示查询条件是一个常数,并且可以通过主键索引快速定位到唯一的一行数据
  2. all(全表扫描): 表示优化器选择了对整个表进行扫描的方式来执行查询操作。这意味着数据库系统将遍历整个表的每一行数据,然后根据查询条件过滤数据,返回符合条件的结果。全表扫描通常在没有合适的索引可用时被选择,或者对大部分数据都需要进行查询的情况下使用。
  3. range(范围访问): 表示优化器选择了对索引进行范围查询的方式来执行查询操作。这种访问类型通常用于根据范围条件(例如 BETWEEN、<、> 等)对索引进行查询,获取符合范围条件的数据行。
  4. index(索引访问): 表示优化器选择了对索引进行直接访问的方式来执行查询操作。这种访问类型通常用于利用索引快速定位到符合查询条件的数据行,而无需扫描整个表。索引访问可以是全覆盖索引或部分索引,取决于索引是否包含了查询所需的所有列。
  5. ref(引用访问): 表示优化器选择了对索引进行引用查询的方式来执行查询操作。这种访问类型通常用于基于索引键值进行查询,并且索引键值与其他表之间存在外键关联关系的情况。引用访问通过索引键值快速定位到符合条件的数据行,然后根据外键关联关系获取关联表中的数据行。
  • 28
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL执行计划(Execution Plan),是指 MySQL 查询优化器生成的一个查询执行计划,用于指导 MySQL 如何执行查询语句并返回结果。执行计划可以帮助我们理解 MySQL 为什么选择某种查询方式,以及如何优化查询语句。 要查看 MySQL 执行计划,可以使用 `EXPLAIN` 命令。例如,要查看某个查询语句的执行计划,可以输入以下命令: ``` EXPLAIN SELECT * FROM mytable WHERE id = 1; ``` 执行该命令后,MySQL 会返回一个表格,其包含了 MySQL 执行该查询语句的详细过程执行计划。表格的各个字段的含义如下: - id:查询的唯一标识符,每个查询都有一个唯一的标识符; - select_type:查询的类型,包括简单查询(SIMPLE)、联合查询(UNION)、子查询(SUBQUERY)等; - table:查询涉及的表的名称; - partitions:查询所涉及的分区; - type:访问类型,即 MySQL 如何访问表,包括全表扫描(ALL)、索引扫描(index)、范围扫描(range)等; - possible_keys:可能使用的索引列表; - key:实际使用的索引; - key_len:使用的索引的长度; - ref:与索引比较的列或常量; - rows:MySQL 预计要扫描的行数; - filtered:返回结果的过滤程度; - Extra:一些额外的信息,例如使用了哪个索引、使用了哪种算法等。 通过查看执行计划,我们可以了解 MySQL执行查询语句时的具体过程,从而进行优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值