浅析一条SQL在mysql中是如何执行的

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

一. Mysql内部组件结构

在这里插入图片描述
MySql大体分为server层和存储引擎层

server层

主要包括连接器、查询缓存、分析器、优化器、执行器等涵盖 MySQL 的大多数核心服务功能以及所有的内置函数如日期、时间、数学和加密函数等所有跨存储引擎的功能都在这一层实现比如存储过程、触发器、视图等。

存储引擎层

存储引擎层负责数据的存储和提取。其架构模式是插件式的支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB它从 MySQL 5.5.5 版本开始成为了默认存储引擎。也就是说如果我们在create table时不指定表的存储引擎类型,默认会给你设置存储引擎为InnoDB。

二.server层介绍

2.1 连接器

我们知道由于MySQL是开源的他有非常多种类的客户端navicat,mysql front,jdbc,SQLyog等非常丰富的客户端,这些客户端要向mysql发起通信都必须先跟Server端建立通信连接而建立连接的工作就是有连接器完成的。

第一步要先连接到这个数据库上这时候接待你的就是连接器。连接器负责跟客户端建立连接、获取权限、维持和管理连接。连接命令一般是这么写的

bogon:~ albert$ mysql -h host[数据库地址] -uxxx[用户] -pxxx[密码] -P 3306

连接命令中的 mysql 是客户端工具用来跟服务端建立连接。在完成经典的 TCP 握手后连接器就要开始认证你的身份这个时候用的就是你输入的用户名和密码。
1、如果用户名或密码不对你就会收到一个"Access denied for user"的错误然后客户端程序结束执行。
2、如果用户名密码认证通过连接器会到权限表里面查出你拥有的权限。之后这个连接里面的权限判断逻辑都将依赖于此时读到的权限。
这就意味着一个用户成功建立连接后即使你用管理员账号对这个用户的权限做了修改也不会影响已经存在连接的权限。修改完成后只有再新建的连接才会使用新的权限设置。用户的权限表在系统表空间的mysql的user表中。
常用mysql指令

mysql> CREATE USER ‘username’@‘host’ IDENTIFIED BY ‘password’; //创建新用户
mysql> grant all privileges on . to ‘username’@‘%’; //赋权限,%表示所有(host)
mysql> flush privileges //刷新数据库
mysql> update user set password=password(”123456″) where user=’root’;(设置用户名密码)
mysql> show grants for root@“%”; 查看当前用户的权限

连接完成后如果你没有后续的动作这个连接就处于空闲状态你可以在 show processlist 命令中看到它。文本中这个图是 show processlist 的结果其中的 Command 列显示为“Sleep”的这一行就表示现在系统里面有一个空闲连接关闭连接 kill 。
在这里插入图片描述
客户端如果长时间不发送command到Server端连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的默认值是 8 小时。
查看wait_timeout

mysql> show global variables like “wait_timeout”;
mysql>set global wait_timeout=28800; //设置全局服务器关闭非交互连接之前等待活动的秒数

如果在连接被断开之后客户端再次发送请求的话就会收到一个错误提醒 Lost connection to MySQL server during query。这时候如果你要继续就需要重连然后再执行请求了。
数据库里面长连接是指连接成功后如果客户端持续有请求则一直使用同一个连接。短连接则是指每次执行完很少的几次查询就断开连接下次查询再重新建立一个。
开发当中我们大多数时候用的都是长连接,把连接放在Pool内进行管理但是长连接有些时候会导致 MySQL 占用内存涨得特别快这是因为 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。所以如果长连接累积下来可能导致内存占用太大被系统强行杀掉OOM从现象看就是 MySQL 异常重启了。
怎么解决这类问题呢

1、定期断开长连接。使用一段时间或者程序里面判断执行过一个占用内存的大查询后断开连接之后要查询再重连。
2、如果你用的是 MySQL 5.7 或更新版本可以在每次执行一个比较大的操作后通过执行 mysql_reset_connection 来重新初始化连接资源。这个过程不需要重连和重新做权限验证但是会将连接恢复到刚刚创建完时的状态。

2.2 查询缓存

常用的一些sql:

mysql>show databases; 显示所有数据库
mysql>use dbname 打开数据库
mysql>show tables; 显示数据库mysql中所有的表
mysql>describe user; 显示表mysql数据库中user表的列信息

连接建立完成后你就可以执行 select 语句了。执行逻辑就会来到第二步查询缓存。
MySQL 拿到一个查询请求后会先到查询缓存看看之前是不是执行过这条语句。之前执行过的语句及其结果可能会以 key-value 对的形式被直接缓存在内存中。key 是查询的语句value 是查询的结果。如果你的查询能够直接在这个缓存中找到 key那么这个 value 就会被直接返回给客户端。
如果语句不在查询缓存中就会继续后面的执行阶段。执行完成后执行结果会被存入查询缓存中。你可以看到如果查询命中缓存MySQL 不需要执行后面的复杂操作就可以直接返回结果这个效率会很高。
大多数情况查询缓存就是个鸡肋为什么呢
因为查询缓存往往弊大于利。查询缓存的失效非常频繁只要有对一个表的更新这个表上所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来还没使用呢就被一个更新全清空了。对于更新压力大的数据库来说查询缓存的命中率会非常低。
一般建议大家在静态表里使用查询缓存什么叫静态表呢就是一般我们极少更新的表。比如一个系统配置表、字典表那这张表上的查询才适合使用查询缓存。好在 MySQL 也提供了这种“按需使用”的方式。你可以将my.cnf参数 query_cache_type 设置成 DEMAND。

my.cnf
#query_cache_type有3个值 0代表关闭查询缓存OFF1代表开启ON2DEMAND代表当sql语句中有SQL_CACHE关键词时才缓存
query_cache_type=2

这样对于默认的 SQL 语句都不使用查询缓存。而对于你确定要使用查询缓存的语句可以用 SQL_CACHE 显式指定像下面这个语句一样

mysql> select SQL_CACHE * from test where ID=5

查看当前mysql实例是否开启缓存机制

mysql> show global variables like "%query_cache_type%";

监控查询缓存的命中率:

mysql> show status like'%Qcache%'; //查看运行的缓存信息

在这里插入图片描述

  • Qcache_free_blocks:表示查询缓存中目前还有多少剩余的blocks如果该值显示较大则说明查询缓存中的内存碎片过多了可能在一定的时间进行整理。
  • Qcache_free_memory:查询缓存的内存大小通过这个参数可以很清晰的知道当前系统的查询内存是否够用是多了还是不够用DBA可以根据实际情况做出调整。
  • Qcache_hits:表示有多少次命中缓存。我们主要可以通过该值来验证我们的查询缓存的效果。数字越大缓存效果越理想。
  • Qcache_inserts: 表示多少次未命中然后插入意思是新来的SQL请求在缓存中未找到不得不执行查询处理执行查询处理后把结果insert到查询缓存中。这样的情况的次数次数越多表示查询缓存应用到的比较少效果也就不理想。当然系统刚启动后查询缓存是空的这很正常。
  • Qcache_lowmem_prunes:该参数记录有多少条查询因为内存不足而被移除出查询缓存。通过这个值用户可以适当的调整缓存大小。
  • Qcache_not_cached: 表示因为query_cache_type的设置而没有被缓存的查询数量。
  • Qcache_queries_in_cache:当前缓存中缓存的查询数量。
  • Qcache_total_blocks:当前缓存的block数量。
    mysql8.0已经移除了查询缓存功能
2.3 分析器

如果没有命中查询缓存就要开始真正执行语句了。首先MySQL 需要知道你要做什么因此需要对 SQL 语句做解析。
分析器先会做“词法分析”。你输入的是由多个字符串和空格组成的一条 SQL 语句MySQL 需要识别出里面的字符串分别是什么代表什么。
MySQL 从你输入的"select"这个关键字识别出来这是一个查询语句。它也要把字符串“T”识别成“表名 T”把字符串“ID”识别成“列 ID”。
做完了这些识别以后就要做“语法分析”。根据词法分析的结果语法分析器会根据语法规则判断你输入的这个 SQL 语句是否满足 MySQL 语法。
如果你的语句不对就会收到“You have an error in your SQL syntax”的错误提醒比如下面这个语句 from 写成了 “rom”。
在这里插入图片描述
词法分析器原理
词法分析器分成6个主要步骤完成对sql语句的分析
1、词法分析
2、语法分析
3、语义分析
4、构造执行树
5、生成执行计划
6、计划的执行
下图是SQL词法分析的过程步骤
在这里插入图片描述
SQL语句的分析分为词法分析与语法分析mysql的词法分析由MySQLLex[MySQL自己实现的]完成语法分析由Bison生成。那么除了Bison外Java当中也有开源的词法结构分析工具例如Antlr4ANTLR从语法生成一个解析器可以构建和遍历解析树可以在IDEA工具当中安装插件antlr v4 grammar plugin。
经过bison语法分析之后会生成一个这样的语法树
在这里插入图片描述
接下来进入到优化器

2.4 优化器

经过了分析器MySQL 就知道你要做什么了。在开始执行之前还要先经过优化器的处理。
优化器是在表里面有多个索引的时候决定使用哪个索引或者在一个语句有多表关联join的时候决定各个表的连接顺序。比如你执行下面这样的语句这个语句是执行两个表的 join

mysql> select * from test1 join test2 using(ID) where test1.name=zhangsan and test2.name=lisi;

既可以先从表 test1 里面取出 name=zhangsan的记录的 ID 值再根据 ID 值关联到表 test2再判断 test2 里面 name的值是否等于 lisi。
也可以先从表 test2 里面取出 name=lisi 的记录的 ID 值再根据 ID 值关联到 test1再判断 test1 里面 name 的值是否等于 zhangsan。
这两种执行方法的逻辑结果是一样的但是执行的效率会有不同而优化器的作用就是决定选择使用哪一个方案。优化器阶段完成后这个语句的执行方案就确定下来了然后进入执行器阶段。

2.5 执行器

开始执行的时候要先判断一下你对这个表 T 有没有执行查询的权限如果没有就会返回没有权限的错误如下所示 (在工程实现上如果命中查询缓存会在查询缓存返回结果的时候做权限验证。查询也会在优化器之前调用 precheck 验证权限)。

mysql> select * from test where id=1;

如果有权限就打开表继续执行。打开表的时候执行器就会根据表的引擎定义去使用这个引擎提供的接口。
比如我们这个例子中的表 test 中ID 字段没有索引那么执行器的执行流程是这样的
1.调用 InnoDB 引擎接口取这个表的第一行判断 ID 值是不是 1如果不是则跳过如果是则将这行存在结果集中
2.调用引擎接口取“下一行”重复相同的判断逻辑直到取到这个表的最后一行。
3.执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
至此这个语句就执行完成了。对于有索引的表执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口之后循环取“满足条件的下一行”这个接口这些接口都是引擎中已经定义好的。你会在数据库的慢查询日志中看到一个 rows_examined 的字段表示这个语句执行过程中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。在有些场景下执行器调用一次在引擎内部则扫描了多行因此引擎扫描行数跟 rows_examined 并不是完全相同的。

三.总结

本文主要讲解mysql语句是如何执行的包括mysql内部组件结构包括server层和引擎层每一层都有哪些操作每步操作都是有什么作用一步步的介绍了sql的执行。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: mysql