一条SQL查询语句是如何执行的?

本篇文章将经过一条 SQL 的执行过程来介绍 MySQL 的基础架构。mysql

首先有一个 user_info 表,表里有一个 id 字段,执行下面这条查询语句:spring

select * from user_info where id = 1;

返回结果为:sql

+----+----------+----------+--------+------+---------------------+---------------------+
| id | username | password | openid | role | create_time         | update_time         |
+----+----------+----------+--------+------+---------------------+---------------------+
| 1  | 武培轩   | 123      | 1      |    1 | 2019-08-29 00:29:08 | 2019-08-29 00:29:08 |
+----+----------+----------+--------+------+---------------------+---------------------+

下面给出 MySQL 的基本架构示意图,能够看出 SQL 语句在 MySQL 的各个模块中的执行过程。数据库

MySQL 基本架构

MySQL 基本架构示意图

大致上,MySQL 分为 Server 层和存储引擎层两部分。缓存

Server 层包括链接器、查询缓存、分析器、执行器等,以及全部的内置函数(如日期、时间、数学和加密函数等)和跨存储引擎的功能(如存储过程、触发器、视图)。架构

存储引擎层负责数据的存储和提取,支持 InnoDB、MyISAM、Memory 等多个存储引擎。MySQL 5.5.5 版本后默认存储存储引擎是 InnoDB。函数

链接器(Connector)

在查询 SQL 语句前,确定要先创建与 MySQL 的链接,这就是由链接器来完成的。链接器负责跟客户端创建链接、获取权限、维持和管理链接。链接命令为:优化

mysql -h$ip -P$port -u$user -p

输入密码,验证经过后,链接器会到权限表里面查出你拥有的权限,以后这个链接里面的权限判断逻辑,都将依赖于此时读到的权限,一个用户成功创建链接后,即便管理员对这个用户的权限作了修改,也不会影响已经存在链接的权限,修改完后,只有再新建的链接才会使用新的权限设置。加密

链接完成后,若是你没有后续的动做,这个链接就处于空闲状态,你能够在 show processlist 命令中看到它。结果以下:日志

+----+------+----------------+------------------+---------+------+----------+------------------+
| Id | User | Host           | db               | Command | Time | State    | Info             |
+----+------+----------------+------------------+---------+------+----------+------------------+
|  3 | root | localhost:2790 | NULL             | Sleep   | 5878 |          | NULL             |
|  4 | root | localhost:2791 | springcloud_sell | Sleep   | 5838 |          | NULL             |
|  7 | root | localhost:2900 | springcloud_sell | Sleep   | 5838 |          | NULL             |
| 10 | root | localhost:3627 | springcloud_sell | Query   |    0 | starting | show processlist |
+----+------+----------------+------------------+---------+------+----------+------------------+

客户端若是太长时间没动静,链接器就会自动将它断开;这个时间是由参数 wait_timeout 控制的,默认值是8小时。若是在链接被断开以后,客户端再次发送请求的话,就会收到一个错误提醒:Lost connection to MySQL server during query

长链接和短链接

  • 数据库里面,长链接是指链接成功后,若是客户端持续有请求,则一直使用同一个链接。
  • 短链接则是指每次执行完不多的几回查询就断开链接,下次查询再从新创建一个。

创建链接的过程一般是比较复杂的,建议在使用中要尽可能减小创建链接的动做,尽可能使用长链接。可是所有使用长链接后,有时候 MySQL 占用内存涨得特别快,这是由于 MySQL 在执行过程当中临时使用的内存是管理在链接对象里面的。这些资源会在链接断
开的时候才释放。因此若是长链接累积下来,可能致使内存占用太大,被系统强行杀掉(OOM),从现象看就是 MySQL 异常重启了。

怎么解决这个问题呢?能够考虑如下两种方案:

  1. 按期断开长链接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开链接,以后要查询再重连。
  2. MySQL 5.7 以上版本,能够在每次执行一个比较大的操做后,经过执行 mysql_reset_connection 来从新初始化链接资源。这个过程不须要重连和从新作权限验证,可是会将链接恢复到刚刚建立完时的状态。

查询缓存(Query Cache)

在创建链接后,就开始执行 select 语句了,执行前首先会查询缓存。

MySQL 拿到查询请求后,会先查询缓存,看是否是执行过这条语句。执行过的语句及其结果会以 key-value 对的形式保存在必定的内存区域中。key 是查询的语句,value 是查询的结果。若是你的查询可以直接在这个缓存中找到 key,那么这个
value 就会被直接返回给客户端。

若是语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。若是查询命中缓存,MySQL 不须要执行后面的复杂操做,就能够直接返回结果,会提高效率。

可是查询缓存的失效很是频繁,只要有对一个表的更新,这个表上全部的查询缓存都会被清空。对于更新压力大的数据库来讲,查询缓存的命中率会很是低。若是业务中须要有一张静态表,很长时间才会更新一次。好比,一个系统配置表,那这张表上的查询才适合使用查询缓存。MySQL 提供了这种按需使用的方式。能够将参数 query_cache_type 设置成 DEMAND,对于默认的 SQL 语句都将不使用查询缓存。而对于你肯定要使用查询缓存的语句,能够用 SQL_CACHE 显式指定,以下:

mysql> select SQL_CACHE * from user_info where id = 1;

MySQL 8.0 版本将查询缓存的功能删除了。

分析器(Analyzer)

若是查询缓存未命中,就要开始执行语句了。首先,MySQL 须要对 SQL 语句进行解析。

分析器先会作词法分析。SQL 语句是由多个字符串和空格组成的,MySQL 须要识别出里面的字符串分别是什么,表明什么。MySQL 从你输入的 select 这个关键字识别出来,这是查询语句。它也要把字符串 user_info 识别成表名,把字符串 id 识别成列名。以后就要作语法分析。根据词法分析的结果,语法分析器会根据语法规则,判断输入的 SQL 语句是否知足 MySQL 语法。

若是你 SQL 语句不对,就会收到 You have an error in your SQL syntax 的错误提醒,好比下面这个语句 from 写成了 form。

mysql> select * form user_info  where id = 1;
1064 - 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 'form user_info  where id = 1' at line 1

通常语法错误会提示第一个出现错误的位置,因此要关注的是紧接 use near 的内容。

优化器(Optimizer)

通过分析器的词法分析和语法分析后,还要通过优化器的处理。

优化器是在表里面有多个索引的时候,决定使用哪一个索引;或者在一个语句有多表关联(join)的时候,决定各个表的链接顺序。好比你执行下面这样的语句,这个语句是执行两个表的 join:

mysql> SELECT * FROM order_master JOIN order_detail USING (order_id) WHERE order_master.pay_status = 0 AND order_detail.detail_id = 1558963262141624521;

既能够先从表 order_master 里面取出 pay_status = 0 的记录的 order_id 值,再根据 order_id 值关联到表 order_detail,再判断 order_detail 里面 detail_id 的值是否等于 1558963262141624521。

也能够先从表 order_detail 里面取出 detail_id = 1558963262141624521 的记录的 order_id 值,再根据 order_id 值关联到 order_master,再判断 order_master 里面 pay_status 的值是否等于 0。

这两种执行方法的逻辑结果是同样的,可是执行的效率会有不一样,而优化器的做用就是决定选择使用哪个方案。优化器阶段完成后,这个语句的执行方案就肯定下来了,而后进入执行器阶段。

执行器(Actuator)

MySQL 经过分析器知道了要作什么,经过优化器知道了该怎么作,因而就进入了执行器阶段,开始执行语句。

开始执行的时候,要先判断一下你对这个表 user_info 有没有执行查询的权限,若是没有,就会返回没有权限的错误,以下所示 (若是命中查询缓存,会在查询缓存返回结果的时候,作权限验证。查询也会在优化器以前调用 precheck 验证权限)。

mysql> select * from user_info where id = 1;
ERROR 1142 (42000): SELECT command denied to user 'wupx'@'localhost' for table 'user_info'

若是有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。好比咱们这个例子中的表 user_info 中,id 字段没有索引,那么执行器的执行流程是这样的:

  1. 调用 InnoDB 引擎接口取这个表的第一行,判断 id 值是否是 1,若是不是则跳过,若是是则将这行存在结果集中;
  2. 调用引擎接口取下一行,重复相同的判断逻辑,直到取到这个表的最后一行。
  3. 执行器将上述遍历过程当中全部知足条件的行组成的记录集做为结果集返回给客户端。

对于有索引的表,第一次调用的是取知足条件的第一行这个接口,以后循环取知足条件的下一行这个接口。

数据库的慢查询日志中有 rows_examined 字段,表示这个语句执行过程当中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。在有些场景下,执行器调用一次,在引擎内部则扫描了多行,所以引擎扫描行数跟 rows_examined 并非彻底相同的。

总结

主要经过对一个 SQL 语句完整执行过程进行讲解,介绍 MySQL 的逻辑架构,MySQL 主要包括链接器、查询缓存、分析器、优化器、执行器这几个模块。

相关文章
相关标签/搜索