@adamhand
2019-02-28T22:33:10.000000Z
字数 2298
阅读 972
下面分析一下一条SQL查询语句的执行过程,语句如下:
mysql> select * from T where ID=10;
下图所示为MySQL基本架构示意图:
MySQL 可以分为 Server 层和存储引擎层两部分。
Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间数学和加密函数等)所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器和视图。
存储引擎层的架构是插拔式的,负责数据的存储和读取。常见的存储引擎有InnoDB、MyIASM和Memory等。InnoDB是默认的存储引擎,但是用户可以通过engine=memory
命令选择其他的存储引擎。
首先需要通过连接器连接到服务器上,连接器负责跟客户端建立连接、获取权限、维持和管理连接。命令如下:
mysql -h$ip -P$port -u$user -p
连接命令中的 mysql 是客户端工具,用来跟服务端建立连接,进行TCP三次握手之后,就需要输入用户名和密码对用户权限进行验证。
如果用户名或密码不对,就会抛出Access denied for user
的错误;如果认证通过,之后这个连接里的操作都会依赖这个权限,即使使用管理员权限对这个用户的权限进行了更改,只要没有登出,当前用户的权限就不会改变。
连接建立之后,就可以开始执行查询语句了。这时首先会查询缓存,之前执行过的SQL语句会以key-value的形式被直接缓存在内存中。key 是查询的语句,value 是查询的结果。
如果此时的查询语句能够寻找到对应的key,那么这个 value 就会被直接返回给客户端;如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。
但是大多数情况下建议不要使用查询缓存,因为查询缓存往往弊大于利。
只要有对一个表的更新,这个表上所有的查询缓存都会被清空。所以对于更新频繁的数据库来说,查询缓存的命中率会非常低。
MySQL提供了一种非常灵活的方式,用户可以选择是不是使用缓存。通过将参数 query_cache_type
设置成 DEMAND
,对于默认的 SQL 语句都不使用查询缓存。而对于确定要使用查询缓存的语句,可以用 SQL_CACHE
显式指定:
mysql> select SQL_CACHE * from T where ID=10;
需要注意的是,MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了。
如果没有命中查询缓存,就要开始真正执行语句了。在执行之前,MySQL会使用分析器对SQL语句做词法分析和语法分析。
词法分析主要是对MySQL语句的关键字进行分析,比如下面的语句:
select SQL_CACHE * from T where ID=10;
通过select
关键字可以判断该SQL语句是查询功能,它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。
语法分析主要是检查SQL语句是否符合MySQL的语法规则,如果不符合就会抛出You have an error in your SQL syntax
错误。
经过分析器的分析后,还要经过优化器的优化。优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)
的时候,决定各个表的连接顺序。比如你执行下面这样的语句,这个语句是执行两个表的 join
:
mysql> select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;
上面的代码有两种执行方法:
这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。
通过优化器优化之后,就会进入执行器执行。
开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示 (在工程实现上,如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证。查询也会在优化器之前调用 precheck 验证权限)。
mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'
如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。
比如上面的例子中的表 T 中,ID 字段没有索引,那么执行器的执行流程是这样的:
至此,这个语句就执行完成了。
对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。
如果表 T 中没有字段 k,而执行了这个语句 select * from T where k=1
, 那肯定是会报“Unknown column ‘k’ in ‘where clause’”
。这个错误是在我们上面提到的哪个阶段报出来的?