2. 解析器:在解析器中对 SQL 语句进行语法分析、语义分析。
如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL需要知道你要做什么,因此需要对SQL语句做解析。SQL语句的分析分为词法分析与语法分析。
分析器先做“ 词法分析 ”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。 MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。
接着,要做“ 语法分析 ”。根据词法分析的结果,语法分析器(比如:Bison)会根据语法规则,判断你输入的这个 SQL 语句是否 满足 MySQL 语法 。
#语法错误:group by后面没有job_id(词对但是组合在一起出现了问题) select department_id,job_id,avg(salary) from employees group by department_id;
如果你的语句不对,就会收到"You have an error in your SQL syntax"的错误提醒,比如语句from写成了"rom"–>词法错误。
# 语句 mysql> select *fro test where id=1; # 错误 ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'fro test where id=1' at line 1 mysql>
如果SQL语句正确,则会生成一个这样的语法树:
下面是SQL词法分析的过程步骤:
至此解析器的工作结束,接下来进入到优化器。
3.优化器:在优化器中会确定 SQL 语句的执行路径,比如是根据全表检索
,还是根据索引检索
等。
经过了解析器,MySQL就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。一条查询可以有很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。
比如:优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序,还有表达式简化、子查询转为连接、外连接转为内连接等。
举例:如下语句是执行两个表的 join:
select * from test1 join test2 using(ID) where test1.name='zhangwei' and test2.name='mysql高级课程';
方案1:可以先从表 test1 里面取出 name='zhangwei’的记录的 ID 值,再根据 ID 值 关联到表test2,再判断test2 里面 name的值是否等于 ‘mysql高级课程’。 方案2:可以先从表 test2 里面取出 name=‘mysql高级课程’ 的记录的 ID 值, 再根据 ID 值关联到 test1,再判断 test1 里面 name的值是否等于 zhangwei。 这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择 使用哪一个方案。优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。 如果你还有一些疑问,比如优化器是怎么选择索引的,有没有可能选择错等。后面讲到索引我们再谈。
在查询优化器中,可以分为逻辑查询 优化阶段和物理查询优化阶段。
逻辑查询优化就是通过改变SQL语句的内容来使得SQL查询更高效,同时为物理查询优化提供更多的候选执行计划。通常采用的方式是对SQL语句进行等价变换,对查询进行重写,而查询重写的数学基础就是关系代数。对条件表达式进行等价谓词重写、条件简化,对视图进行重写,对子查询进行优化,对连接语义进行了外连接消除、嵌套连接消除等。
物理查询优化是基于关系代数进行的查询重写,而关系代数的每一步都对应着物理计算,这些物理计算往往存在多种算法。因此需要计算各种物理路径的代价,从中选择代价最小的作为执行计划。在这个阶段里,对于单表和多表连接的操作,需要高效地使用索引,提升直询效率。
4. 执行器:
截止到现在,还没有真正去读写真实的表,仅仅只是产出了一个执行计划。于是就进入了执行器阶段
。
在执行之前需要判断该用户是否具备权限
。如果没有,就会返回权限错误。如果具备权限,就执行 SQL查询并返回结果。在 MySQL8.0 以下的版本,如果设置了查询缓存,这时会将查询结果进行缓存。
select * from test where id=1;
如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,调用存储引擎API
对表进行的读写。存储引擎API
只是抽象接口,下面还有个存储引擎层,具体实现还是要看表选择的存储引擎。
比如:表 test 中,ID 字段没有索引,那么执行器的执行流程是这样的:
调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是1,如果不是则跳过,如果是则将这行存在结果集中; 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
至此,这个语句就执行完成了。对于有索引的表,执行的逻辑也差不多。
SQL 语句在 MySQL 中的流程是: SQL语句→查询缓存→解析器→优化器→执行器
。
2.2 MySQL8中SQL执行原理
前面的结构图很复杂,我们需要抓取最核心的部分:SQL的执行原理。不同的DBMS的SQL的执行原理是相通的,只是在不同的软件中,各有各的实现路径。
既然一条SQL语句会经历不同的模块,那我们就来看下,在不同的模块中。SQL执行所使用的资源(时间)是怎样的。如何在MySQL中对一条 SQL语句的执行时间进行分析。
1.确认profiling 是否开启
了解查询语句底层执行的过程: select @@profiling;或者show variables like '%profiling%'查看是否开启计划。开启它可以让MySQL收集在SQL执行时所使用的资源情况,命令如下:
select @@profiling; /* +-------------+ | @@profiling | +-------------+ | 0 | +-------------+ */ show variables like 'profiling'; /* +---------------+-------+ | Variable_name | Value | +---------------+-------+ | profiling | OFF | +---------------+-------+ */
profiling=0 代表关闭,我们需要把 profiling 打开,即设置为 1:
set profiling=1; select @@profiling; /* +-------------+ | @@profiling | +-------------+ | 1 | +-------------+ */
Profiling功能由MySQL会话变量:profiling控制。默认是OFF(关闭状态)
2.多次执行相同SQL查询
然后我们执行一个 SQL 查询(你可以执行任何一个 SQL 查询):
select * from employees;
3.查看profiles
show profiles和show profile语句可以展示当前会话(退出session后,profling重置为o)中执行语句的资源使用情况。
查看当前会话所产生的所有 profiles:
show profiles; # 显示最近的几次查询 /*本机输出: +----------+------------+-------------------------+ | Query_ID | Duration | Query | +----------+------------+-------------------------+ | 1 | 0.00031625 | select @@profiling | | 2 | 0.00019625 | select * from employees | +----------+------------+-------------------------+ */
4.查看profile
显示执行计划,查看程序的执行步骤:
默认是最近的一次
当然你也可以查询指定的 Query ID,比如:
show profile for query 7;
show profile cpu,block io for query 6;
可以看出都是17条,所以是间接证明8.0是没有开启缓存的
1、除了查看cpu、io阻塞等参数情况,还可以查询下列参数的利用情况。
Syntax : SHOW PROFILE [type [,type] ... ] [ FOR QUERY n] [LIMIT row_count [OFFSET offset]] type : { ALL --显示所有参数的开销信息 BLOCK IO --显示IO的相关开销 CONTEXT SWITCHES -- 上下文切换和关开信息 CPU --显示CPU的相关开销 IPC --显示发送和接收相关开销信息 MEMORY --显示内存相关开销信息 PAGE FAULTS -- 显示页面错读和关开销信息 SOURCE -- 显示和Saurce_function, Source_file , Source_line 相关的开销信息 SWAPS -- 显示交换次数相关的开销信息 }
2.3 MySQL5.7中SQL执行原理
上述操作在MySQL5.7中测试,发现前后两次相同的sql语句,执行的查询过程仍然是相同的。不是会使用缓存吗?这里我们需要显式开启查询缓存模式 。
在MySQL5.7中如下设置:
1.配置文件中开启查询缓存
在 /etc/my.cnf 中新增一行:
query_cache_type=1
2.重启mysql服务
systemctl restart mysqld
3.开启查询执行计划
由于重启过服务,需要重新执行如下指令,开启profiling。
set profiling=1;
4.执行语句两次:
select * from departments; select * from departments;
5.查看profiles
6.查看profile
显示执行计划,查看程序的执行步骤:
show profile for query 7;
show profile for query 8;
结论不言而喻。执行编号8时,比执行编号7时少了很多信息,可以看出查询语句直接从缓存中获取数据。
- 注意1:
SQL必须是一致的,否则,不能命中缓存。
例如:
#虽然查询结果一致。但并没有命中缓存 select * from mydb.mytbl where id=2 select * from mydb.mytbl where id>1 and id<3
- 注意2:
同样的开启缓存的配置信息query_cache_type=1
如果在MySQL8中添加。重启服务时会报错:
[ root@atguigue1 ~]# vim /etc/my . cnf [ root@atguigu01 ~]# systemctl restart mysqld ; Job for mysqld.service failed because the control process exited with error code. See"systemctl status mysqld.service" and "journalctl -xe" for details.
分别在MySQL5.7和 MySQL8中执行如下命令:
mysql> show variables like ' %query_cache%' ;
- MysQL5.7中显示:
MysQL8.0中显示:
2.4 SQL语法顺序
随着Mysql版本的更新换代,其优化器也在不断的升级,优化器会分析不同执行顺序产生的性能消耗不同而动态调整执行顺序。
需求:查询每个部门年龄高于20岁的人数且高于20岁人数不能少于2人,显示人数最多的第一名部门信息下面是经常出现的查询顺序:
下面是经常出现的查询顺序:
2.5 Oracle中的SQL执行流程(了解)
Oracle 中采用了共享池
来判断 SQL 语句是否存在缓存和执行计划,通过这一步骤我们可以知道应该采用硬解析还是软解析。
我们先来看下 SQL 在 Oracle 中的执行过程:
从上面这张图中可以看出,SQL 语句在 Oracle 中经历了以下的几个步骤。
1.语法检查:检查 SQL 拼写是否正确,如果不正确,Oracle 会报语法错误。
2.语义检查:检查 SQL 中的访问对象是否存在。比如我们在写 SELECT 语句的时候,列名写错了,系统就会提示错误。语法检查和语义检查的作用是保证 SQL 语句没有错误。
3.权限检查:看用户是否具备访问该数据的权限。
4.共享池检查:共享池(Shared Pool)是一块内存池,最主要的作用是缓存 SQL 语句和该语句的执行计划。Oracle 通过检查共享池是否存在 SQL 语句的执行计划,来判断进行软解析,还是硬解析。那软解析和硬解析又该怎么理解呢?
在共享池中,Oracle 首先对 SQL 语句进行 Hash 运算 ,然后根据 Hash 值在库缓存(Library Cache)中查找,如果存在 SQL 语句的执行计划 ,就直接拿来执行,直接进入“执行器”的环节,这就是 软解析 。
如果没有找到 SQL 语句和执行计划,Oracle 就需要创建解析树进行解析,生成执行计划,进入“优化器”这个步骤,这就是硬解析 。
5.优化器:优化器中就是要进行硬解析,也就是决定怎么做,比如创建解析树,生成执行计划。
.
6.执行器:当有了解析树和执行计划之后,就知道了 SQL 该怎么被执行,这样就可以在执行器中执行语句了。
共享池是 Oracle 中的术语,包括了库缓存,数据字典缓冲区等。我们上面已经讲到了库缓存区,它主要缓存 SQL 语句和执行计划。而 数据字典缓冲区 存储的是 Oracle 中的对象定义,比如表、视图、索引等对象。当对 SQL 语句进行解析的时候,如果需要相关的数据,会从数据字典缓冲区中提取。
库缓存 这一个步骤,决定了 SQL 语句是否需要进行硬解析。为了提升 SQL 的执行效率,我们应该尽量避免硬解析,因为在 SQL 的执行过程中,创建解析树,生成执行计划是很消耗资源的。
你可能会问,如何避免硬解析,尽量使用软解析呢?
在 Oracle 中, 绑定变量是它的一大特色。绑定变量就是在 SQL 语句中使用变量,通过不同的变量取值来改变 SQL 的执行结果。这样做的好处是能提升软解析的可能性 ,不足之处在于可能会导致生成的执行计划不够优化,因此是否需要绑定变量还需要视情况而定。
举个例子,我们可以使用下面的查询语句:
select * from player where player_id = 10001;
你也可以使用绑定变量,如:
select * from player where player_id = :player_id;
这两个查询语句的效率在 Oracle 中是完全不同的。如果你在查询 player_id = 10001 之后,还会查询10002、10003 之类的数据,那么每一次查询都会创建一个新的查询解析。而第二种方式使用了绑定变量,那么在第一次查询之后,在共享池中就会存在这类查询的执行计划,也就是软解析。
因此,我们可以通过使用绑定变量来减少硬解析,减少 Oracle 的解析工作量。但是这种方式也有缺点,使用动态 SQL 的方式,因为参数不同,会导致 SQL 的执行效率不同,同时 SQL 优化也会比较困难。