查询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
分析成表名 T
,id
识别成列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;