【MySQL进阶】执行一条 sql 语句,期间会发生什么

news2025/1/11 10:07:45

【MySQL进阶】执行一条 sql 语句,期间会发生什么?

文章目录

  • 【MySQL进阶】执行一条 sql 语句,期间会发生什么?
    • MySQL 执行流程是怎样的?
    • 第一步:连接器
    • 第二步:查询缓存
    • 第三步:解析 SQL
      • 解析器
    • 第四步:执行 SQL
      • 预处理器
      • 优化器
      • 执行器
        • 主键索引查询
        • 全表扫描
        • 索引下推
    • 总结

学习 MySQL 的时候,大家肯定第一个先学到的就是 sql语句了。

但是有没有想过,MySQL 执行一条 sql查询语句,在 MySQL 中期间发生了什么?

MySQL 执行流程是怎样的?

先来一个上帝视角图,下面就是 MySQL 执行一条 SQL 查询语句的流程,也从图中可以看到 MySQL 内部架构里的各个功能模块。

在这里插入图片描述

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

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

第一步:连接器

如果你在 Linux 操作系统里要使用 MySQL,那你第一步肯定是要先连接 MySQL 服务,然后才能执行 SQL 语句,普遍我们都是使用下面这条命令进行连接:

mysql -u$user -p

连接的过程需要先经过 TCP 三次握手,因为 MySQL 是基于 TCP 协议进行传输的,如果 MySQL 服务并没有启动,则会收到如下的报错:

image-20230108222052766

如果 MySQL 服务正常运行,完成 TCP 连接的建立后,连接器就要开始验证你的用户名和密码,如果用户名或密码不对,就收到一个"Access denied for user"的错误,然后客户端程序结束执行。

image-20230108222108567

如果用户密码都没有问题,连接器就会获取该用户的权限,然后保存起来,后续该用户在此连接里的任何操作,都会基于连接开始时读到的权限进行权限逻辑的判断。

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

MySQL 的连接数有限制吗?

MySQL 服务支持的最大连接数由 max_connections 参数控制,比如我的 MySQL 服务默认是 151 个,超过这个值,系统就会拒绝接下来的连接请求,并报错提示“Too many connections”。

mysql> show variables like 'max_connections';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 151   |
+-----------------+-------+
1 row in set (0.00 sec)

MySQL 的连接也跟 HTTP 一样,有短连接和长连接的概念,它们的区别如下:

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

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

可以看到,使用长连接的好处就是可以减少建立连接和断开连接的过程,所以一般是推荐使用长连接。

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

怎么解决长连接占用内存的问题?

有两种解决方式。

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

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

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

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

第二步:查询缓存

连接器得工作完成后,客户端就可以向 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。

这里说的查询缓存是 server 层的,也就是 MySQL 8.0 版本移除的是 server 层的查询缓存,并不是 Innodb 存储引擎中的 buffer pool。

第三步:解析 SQL

在正式执行 SQL 查询语句之前, MySQL 会先对 SQL 语句做解析,这个工作交由「解析器」来完成。

解析器

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

第一件事情,词法分析。MySQL 会根据你输入的字符串识别出关键字出来,构建出 SQL 语法树,这样方便后面模块获取 SQL 类型、表名、字段名、 where 条件等等。

第二件事情,语法分析。根据词法分析的结果,语法解析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。

如果我们输入的 SQL 语句语法不对,就会在解析器这个阶段报错。比如,我下面这条查询语句,把 from 写成了 form,这时 MySQL 解析器就会给报错。

image-20230108222432887

但是注意,表不存在或者字段不存在,并不是在解析器里做的,《MySQL 45 讲》说是在解析器做的,但是MySQL 源码(5.7和8.0)得出结论是解析器只负责构建语法树和检查语法,但是不会去查表或者字段存不存在。

第四步:执行 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 查询语句的执行方案确定下来,比如在表里面有多个索引的时候,优化器会基于查询成本的考虑,来决定选择使用哪个索引。

执行器

经历完优化器后,就确定了执行方案,接下来 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 的函数,所以当调用该函数的时候,执行器就退出循环,也就是结束查询了。

至此,这个语句就执行完成了。

全表扫描

举个全表扫描的例子:

select * from product where name = 'iphone';

这条查询语句的查询条件没有用到索引,所以优化器决定选用访问类型为 ALL 进行查询,也就是全表扫描的方式查询,那么这时执行器与存储引擎的执行流程是这样的:

  • 执行器第一次查询,会调用 read_first_record 函数指针指向的函数,因为优化器选择的访问类型为 all,这个函数指针被指向为 InnoDB 引擎全扫描的接口,让存储引擎读取表中的第一条记录
  • 执行器会判断读到的这条记录的 name 是不是 iphone,如果不是则跳过;如果是则将记录发给客户的(是的没错,Server 层每从存储引擎读到一条记录就会发送给客户端,之所以客户端显示的时候是直接显示所有记录的,是因为客户端是等查询语句查询完成后,才会显示出所有的记录)。
  • 执行器查询的过程是一个 while 循环,所以还会再查一次,会调用 read_record 函数指针指向的函数,因为优化器选择的访问类型为 all,read_record 函数指针指向的还是 InnoDB 引擎全扫描的接口,所以接着向存储引擎层要求继续读刚才那条记录的下一条记录,存储引擎把下一条记录取出后就将其返回给执行器(Server层),执行器继续判断条件,不符合查询条件即跳过该记录,否则发送到客户端;
  • 一直重复上述过程,直到存储引擎把表中的所有记录读完,然后向执行器(Server层) 返回了读取完毕的信息;
  • 执行器收到存储引擎报告的查询完毕的信息,退出循环,停止查询。

至此,这个语句就执行完成了。

索引下推

在这部分非常适合讲索引下推(MySQL 5.6 推出的查询优化策略),这样大家能清楚的知道,「下推」这个动作,下推到了哪里。

索引下推能够减少二级索引在查询时的回表操作,提高查询的效率,因为它将 Server 层部分负责的事情,交给存储引擎层去处理了。

举一个具体的例子,方便大家理解,这里一张用户表如下,我对 age 和 reward 字段建立了联合索引(age,reward):

image-20230108222745645

现在有下面这条查询语句:

select * from t_user  where age > 20 and reward = 100000;

联合索引当遇到范围查询 (>、<) 就会停止匹配,也就是 age 字段能用到联合索引,但是 reward 字段则无法利用到索引。具体原因这里可以看这篇:【MySQL进阶】什么是索引下推优化?

那么,不使用索引下推(MySQL 5.6 之前的版本)时,执行器与存储引擎的执行流程是这样的:

  • Server 层首先调用存储引擎的接口定位到满足查询条件的第一条二级索引记录,也就是定位到 age > 20 的第一条记录;
  • 存储引擎根据二级索引的 B+ 树快速定位到这条记录后,获取主键值,然后进行回表操作,将完整的记录返回给 Server 层;
  • Server 层在判断该记录的 reward 是否等于 100000,如果成立则将其发送给客户端;否则跳过该记录;
  • 接着,继续向存储引擎索要下一条记录,存储引擎在二级索引定位到记录后,获取主键值,然后回表操作,将完整的记录返回给 Server 层;
  • 如此往复,直到存储引擎把表中的所有记录读完。

可以看到,没有索引下推的时候,每查询到一条二级索引记录,都要进行回表操作,然后将记录返回给 Server,接着 Server 再判断该记录的 reward 是否等于 100000。

而使用索引下推后,判断记录的 reward 是否等于 100000 的工作交给了存储引擎层,过程如下 :

  • Server 层首先调用存储引擎的接口定位到满足查询条件的第一条二级索引记录,也就是定位到 age > 20 的第一条记录;
  • 存储引擎定位到二级索引后,先不执行回表操作,而是先判断一下该索引中包含的列(reward列)的条件(reward 是否等于 100000)是否成立。如果条件不成立,则直接跳过该二级索引。如果成立,则执行回表操作,将完成记录返回给 Server 层。
  • Server 层在判断其他的查询条件(本次查询没有其他条件)是否成立,如果成立则将其发送给客户端;否则跳过该记录,然后向存储引擎索要下一条记录。
  • 如此往复,直到存储引擎把表中的所有记录读完。

可以看到,使用了索引下推后,虽然 reward 列无法使用到联合索引,但是因为它包含在联合索引(age,reward)里,所以直接在存储引擎过滤出满足 reward = 100000 的记录后,才去执行回表操作获取整个记录。相比于没有使用索引下推,节省了很多回表操作。

当你发现执行计划里的 Extr 部分显示了 “Using index condition”,说明使用了索引下推。

image-20230108222838737

总结

执行一条 SQL 查询语句,期间发生了什么?

  • 连接器:建立连接,管理连接、校验用户身份;
  • 查询缓存:查询语句如果命中查询缓存则直接返回,否则继续往下执行。MySQL 8.0 已删除该模块;
  • 解析 SQL,通过解析器对 SQL 查询语句进行词法分析、语法分析,然后构建语法树,方便后续模块读取表名、字段、语句类型;
  • 执行 SQL:执行 SQL 共有三个阶段:
    • 预处理阶段:检查表或字段是否存在;将 select * 中的 * 符号扩展为表上的所有列。
    • 优化阶段:基于查询成本的考虑, 选择查询成本最小的执行计划;
    • 执行阶段:根据执行计划执行 SQL 查询语句,从存储引擎读取记录,返回给客户端;

怎么样?现在再看这张图,是不是很清晰了。

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

Google Play Install Referrer API 和 Facebook App Ads Referral 集成

介绍&#xff1a;由于需要精准的获取投放广告的数据&#xff08;投放的平台&#xff0c;防止数据的丢失等&#xff09;。我们使用了Google Play Install Referrer API 和Facebook App Ads Referral结合采集数据&#xff0c;然后通过后台服务记录数据。最后服务器记录的数据与Fa…

openwrt tc(traffic contrl)配置及使用

在某些情况下&#xff0c;嵌入式设备需要用到弱网环境进行测试。网上针对tc的配置有很多&#xff0c;但是7688的没找到&#xff0c;所以做个记录&#xff0c;方便大家使用。netem配置 Kernel modules > Network Support > kmod-netemqos-scripts配置> Base system >…

甲氧基聚乙二醇-葡聚糖 mPEG-Dextran结构式;Dextran-PEG科研实验用

甲氧基聚乙二醇-葡聚糖 mPEG-Dextran 中文名称&#xff1a;甲氧基聚乙二醇-葡聚糖 英文名称&#xff1a;mPEG-Dextran 规格&#xff1a;500mg/1g/5g/10g 分散系数&#xff1a;小于等于1.05 分子量&#xff08;PEG&#xff09;&#xff1a;2000&#xff1b;3400&#xff1b…

Jmeter5.0之 dubbo插件安装

我记得之前 这个链接&#xff1a;https://github.com/ningyu1/jmeter-plugins-dubbo/tree/master/dist 能下载现成的dubbo插件&#xff0c;现在这个链接访问后提示404、网上搜索提供了最新的获取方式&#xff0c;1、访问链接&#xff1a;https://github.com/ningyu1/jmeter-plu…

ForkJoin

什么是ForkJoin ForkJoin在JDK1.7&#xff0c;并行执行任务&#xff01;提高效率&#xff0c;大数据量&#xff01; 大数据&#xff1a;Map Reduce&#xff08;把大任务拆分为小任务&#xff09; ForkJoin特点&#xff1a;工作窃取 这个里面维护的都是双端队列 当第一行的任务执…

AJAX

目录1 AJAX 快速入门1.1 介绍1.2 原生 JavaScript 实现 AJAX1.3 原生JS实现AJAX详解1.4 jQuery 的 GET 方式实现 AJAX1.5 jQuery 的 POST方式实现 AJAX1.5 jQuery 的 通用方式实现 AJAX2 JSON 的处理2.1 JSON 回顾2.2 JSON 转换工具的介绍2.3 JSON转换练习3 案例-搜索联想4 案例…

git 切换分支

1. 查看远程分支&#xff0c;查看当前所有git branch -a 2. 查看本地分支git branch * master3. 切换分支 //创建分支 git checkout -b test origin/test //切换分支 git checkout 分支名

iframe 页面间的通信(分享)

文章目录1. 概述2. 页面之间传递数据3. 直接获取父页面的数据4. 获取父级页面源的信息1. 概述 在许多老的项目中使用的都是 iframe 进行的页面嵌套&#xff0c;我这里写了三个页面进行演示红色的页面为最外层&#xff0c;本地开的服务&#xff0c;端口为 5500&#xff0c;而黄色…

官宣了,德云社京剧神童陶阳亮相河南春晚,为什么不是老乡岳云鹏

随着春节的临近&#xff0c;一年一度的央视春晚&#xff0c;也即将拉开大幕与观众见面&#xff0c;不过今年却略有不同。虽然央视春晚还没有开播&#xff0c;互联网上面就一片抵触声音&#xff0c;有人甚至给出了建议&#xff0c;希望省下钱给大家发红包。 与央视春晚形成鲜明对…

未来的分析型数据库是什么样的?

本文来自于 Doris Summit 2022 演讲实录&#xff0c;演讲人&#xff1a;陈明雨十年对于数据库意味着什么&#xff1f;身处在日新月异的时代&#xff0c;我们见惯了技术的兴起与繁荣、变迁与衰落&#xff0c;甚至是朝荣夕灭。信息技术以前所未有的速度更迭&#xff0c;给周遭事物…

从0到1完成一个Vue后台管理项目(十四、对话框封装、接口联通、api优化 很重点(封装思想))

往期 从0到1完成一个Vue后台管理项目&#xff08;一、创建项目&#xff09; 从0到1完成一个Vue后台管理项目&#xff08;二、使用element-ui&#xff09; 从0到1完成一个Vue后台管理项目&#xff08;三、使用SCSS/LESS&#xff0c;安装图标库&#xff09; 从0到1完成一个Vu…

React Fiber 树思想,解决业务实际场景

熟悉 Fiber 树结构 我们知道&#xff0c;React 从 V16 版本开始采用 Fiber 树架构来实现渲染和更新机制。 Fiber 在 React 源码中可以看作是一个任务执行单元&#xff0c;每个 React Element 都会有一个与之对应的 Fiber 节点。 Fiber 节点的核心数据结构如下&#xff1a; …

基于javaweb+springboot+HTML汽车配件管理系统设计和实现以及文档报告

基于javaweb(springboot)汽车配件管理系统设计和实现以及文档报告 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言…

Java面试题,这是一篇会一直更新的博客!欢迎大家留言~

Java面试题1、面向对象与面向过程2、Spring源码分析&#xff08;可能比较复杂&#xff0c;但是看下去可能会找到让你顿悟的字眼&#xff09;2.1、Spring创建Bean对象2.2 实例化&#xff08;推断构造方法&#xff09;1、面向对象与面向过程 封装&#xff1a;封装在于明确标识出允…

快递驿站取件管理系统|基于SpringBoot的快递栈系统设计与实现

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

商场楼层地图怎么制作,高效、便捷的商场二三维地图绘制平台

当顾客走进庞大的商场购物中心&#xff0c;如何让顾客在商场购物消费时有更轻松方便的体验&#xff1f;如何能快速将品牌和商品传递给潜在顾客&#xff0c;并一键引导顾客到店&#xff1f;电子地图作为大家最喜闻乐见的高效应用形式&#xff0c;在商业应用中&#xff0c;不论针…

arduinoIDE下载keypad和password库文件

任务需求&#xff1a;需要使用4*4矩阵键盘来完成一个按键密码锁 需要使用keypad和password这两个开源库 开源库的查找 1.在IDE中直接查找 同时在更多中能够看到api文档 2.在官网中进行找寻 至于导入的方式比较简单就不做叙述 keypad库文件文档api分析 api可以在官网中有…

用Python找出了删除自己微信的所有人并将他们自动化删除了

用Python找出了删除自己微信的所有人并将他们自动化删除了 你是否有微信被删了好友不自知&#xff0c;还傻傻的给对方发消息&#xff0c;结果出现了下图中那尴尬的一幕的经历呢&#xff1f;其实我们可以用Python提前把他们找出来并自动化删除避免尴尬的。 为了避免再次出现上…

Tools for better thinking

内容来自untools官网&#xff0c;感兴趣的可以去翻译一下&#xff0c;我只做下总结 理解系统 Concept mapConnection circlesIceberg ModelBalancing feedback loopReinforcing feedback loop 做出决策 Cynefin frameworkSix Thinking HatsSecond-order thinkingEisenhower Mat…

【LeetCode】753. 破解保险箱

753. 破解保险箱 题目描述 有一个需要密码才能打开的保险箱。密码是 n 位数, 密码的每一位是 k 位序列 0, 1, ..., k-1 中的一个 。 你可以随意输入密码&#xff0c;保险箱会自动记住最后 n 位输入&#xff0c;如果匹配&#xff0c;则能够打开保险箱。 举个例子&#xff0c…