MySQL查询语句过程透明化

查询SQL从输入SQL到返回结果集,都经历了什么?

  • 有一个最简单的表 T,表里只有一个字段ID, 执行以下 SQL时, MySQL内部的执行过程是怎样的?
select * from T where id = 10;

首先看一下MySQL的基本架构示意图,从中可以清楚的看到SQL语句在MySQL的各个功能模块中的执行过程;


架构示意图

大体来说 MySQL可以分为 server层和存储引擎层两个部分;

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

存储引擎层:负责数据的存储和提取。其架构模式是插件式的,支持 innoDB,MyISAM,Memory等多个存储引擎。电子最常用的存储引擎是 InnoDB,它是从 MySQL5.5.5版本开始默认的存储引擎;

从上图可以看出不同的存储引擎公用一个 server层,也就是从连接器到执行器的部分。

  • 连接器

第一步,首先会连接到这个数据库上,这时候就接触到了连接器;
连接器负责跟客户端建立连接,获取权限,维持和管理连接。
mysql -h$ip -P$port -u$user -p
输入完命令后,需要在交互规划里输入密码。

连接命令中MySQL是客户端的工具,用来和服务端建立连接的。在完成 tcp 握手后,连接器就开始认证你的身份,这时就是对你用户名和密码校验;

  • 如果用户名或者密码不对,就会报出 Access denied for user 的错误,然后客户端程序结束执行;
  • 如果用户名密码认证通过,连接器回到权限表里查出你拥有的权限,之后,这个连接里面的权限判断逻辑,都将依赖此时督导的权限。

这意味着:一个用户成功建立连接后,及时你用管理员的账号对这个用户权限做了修改,也不会影响已经存在的连接逻辑,都将依赖于此时读到的权限;修改完之后,只有再新建的连接才会使用新的权限设置;

连接完成后,如果灭有后续的动作,这个连接线程就处于空闲的状态,可以使用 show processlist 命令查看这条命令;


其中 Command 列显示为 sleep 的这一行,就表示在系统里面处于空闲状态;

  • 客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是有参数 wait_timeout 来控制的,默认是 8小时;
  • 如果连接被断开之后,客户端再次发送请求的话,就会报错 Lost connection to mysql server during query;这个时候如果需要继续的话就得重新连接,然后才可以之心请求;
  • 数据库里面,长链接 : 是指连接成功后,如果客户端持续有请求,则一直使用同一个连接;
    短连接是指每次执行完很少的几次查询就断开连接,下次查询在重新建立一个
  • 建立连接的过程通常比较复杂,建议在使用中药尽量减少建立连接的动作,尽量使用长连接;

全部使用长连接,可能会发现,有时 MySQL 占用内存涨的很快,这是因为 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。所有如果长连接积累下来,可能导致内存占用太大,被系统强行杀掉 (OOM),从现象看就是 MySQL 异常重启了;

这个问题怎么解决呢,可以考虑一下两种方案

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

执行完连接器,就可以执行 select 语句了。执行逻辑来到 查询缓存

MySQL拿到一个语句时,先到查询缓存中查找之前是不是执行过这个查询。之前执行过的语句会以 key value 对的形式保存在查询缓存中,key 是查询语句, value 是查询结果。如果查询可以直接在查询缓存中命中结果,则直接返回 value 结果集给客户端;

如果没有命中查询缓存中的 key ,MySQL则继续进行查询,然后将查询结果缓存一份到查询缓存中。可以看到如果 查询时命中查询缓存时,就会直接返回数据,不需要进行后面的查询,这个效率很高。

  • 但是实际工作中 不推荐使用查询缓存 ,因为查询缓存往往是弊大于利;
    查询缓存的失效非常频繁,只要是对表中一个进行更新,整个查询缓存会被清空。因此很可能你之前缓存的数据,还没到使用呢,就被清空了。
    对于更新频繁的表(库)来说,缓存的命中率非常低。除非有一张静态表,更新频率很低,比如一张配置表,很少修改其中的配置,这张表才适合使用查询缓存。

MySQL 有一个 按需使用 的配置 。 可以将参数 query_cache_type 设置成 DEMAND ,这样对于默认的 SQL语句都不会使用查询缓存。对于确定要用查询缓存的语句可以显示的使用 query_cache 指定;

select QUERY_CACHE * from T where id = 10;

注意: MySQL 8.0 版本直接将查询缓存删掉了,8.0 版本以后没有查询缓存了。

  • 分析器

如果没有命中查询缓存,就开始正真执行语句了。首先MySQL需要知道你要做什么,因此会根据SQL语句进行解析;

  • 分析器会首先进行 词法分析,MySQL 把输入的多个字符串和空格识别出来,每个字符串是什么,代表什么。
    MySQL 把 select 这个关键字识别出来,然后把 T 分析成 表名 Tid 识别成 列ID
  • 然后进行 语法分析 ,根据词法分析的结构,语法分析会根据语法规则,判断输入的语句是否符合 MySQL语法。
    例如
mysql> elect * from t 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 'elect * from t where ID=1' at line 1

一般语法错误会提示第一个出现错误的位置,所以要关注的是 use near 后面的。

  • 优化器

在开始执行之前,还要经过优化器的处理;
优化器是在表中有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联的时候,决定各表的连接顺序。
例如:

select * from a inner join b on a.id = b.id where a.name = 'tom' and b.class = 3;
  • 既可以先取出 a 表中 name = Tom 的数据然后根据 ID 连接 b 表取出 class = 3 的数据。
  • 还可以 先取出 b 表中 class = 3 的数据然后根据 ID 连接 a 表 取出 name = tom 的数据。
    两者的查询结果是一样的,但是执行效率是不一样的。优化器的作用就是选择哪一种执行方案;

优化器阶段完成后,这个语句的执行方案就确定了,然后就是执行器阶段。

  • 执行器

MySQL 通过分析器知道了要做什么,通过优化器知道了怎么做,然后就是执行器阶段了,开始正真执行SQL语句。

开始执行的时候,首先判断一下当前用户有没有这张表的查询权限,如果没有权限就会报错;

mysql> select * from T where ID=10;

ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'

如果有权限,就打开表继续执行。打开表的时候,会根据表的引擎定义,去使用这个引擎的接口;
如这个表 T 中,id 列没有索引,执行器的执行流程是这样的。

  • 调用 InnoDB 引擎的接口取出表的第一行,判断ID是不是 10 如果不是则跳过,如果是则把这行保存到结果集中;
  • 调用引擎接口调用 '下一行' ,重复相同的逻辑,直到取出表的最后一行;
  • 执行器将上述遍历过程中所有满足条件的行,组成记录集作为结果集返回给客户端。
    至此这条 查询 SQL 就执行完了。

对于有索引的表,逻辑也差不多。第一次调用的是 取出满足条件的第一行 这个接口,然后循环取出 满足条件的下一行 这个接口,这些接口都是引擎定义好的。

在慢查询日志中会看到 rows_examined 的字段,表示这个查询扫描了多少行。这个值就是执行器每次调用接口后累加的。

一个问题:
在以下查询语句中, T 表中不存在字段 k ,会报错 Unknown column ‘k’ in ‘where clause ,请根据上文回答,这个错误是哪个阶段报出的?

select * from T where k = 10;

你可能感兴趣的:(MySQL查询语句过程透明化)