MySQL 架构
MySQL 架构可以分为 Server 层、存储引擎两部分。如下图所示:
从图中可以看出:
Server 层包括连接器、查询缓存、分析器、优化器、执行器。包含所有内置函数,所有跨存储引擎的功能都在这里实现,如存储过程、视图、触发器等。
存储引擎层负责数据的存储和提取。常见的存储引擎:MyISAM、InnoDB、Memory。
查询语句执行过程
接下来我们就来看看一条查询语句是如何执行的。以下面这个 SQL 语句为例:
select * from t where id = 1;
连接器
首先我们要登录 MySQL,输入登录命令 mysql -uroot -p
,通过 TCP 三次握手来跟服务端建立连接,此时,连接器就开始校验输入的用户名与密码。
- 如果用户名与密码不正确,会返回
Access denied for user
的错误,然后客户端程序结束执行 - 用户名与密码正确,连接器会到权限表中查询此用户拥有的权限,之后的操作都依赖此时得到的权限,如果连接后修改了此用户的权限,也不会影响这次连接的权限,直到建设新的连接后才会使用新的权限。
若客户端长时间没有操作则会断开连接,由参数 wait_timeout 控制,默认为 8 小时。在连接断开之后,如果客户端再次发送请求的话,就会收到一个错误提醒: Lost connection to MySQL server during query
。
查询缓存
连接建立后,可以使用查询语句了。
此时,来到第二步:查询缓存。
之前执行过的语句及其结果可能会以 key-value对 的形式,被直接缓存在内存中。key 是查询的语句,value 是查询的结果。如果查询语句能够直接在这个缓存中找到key,那么就会直接返回给客户端对应的 value,不需要在往后执行了。
但是,缓存的缺点在于:只要有对一个表的更新,这个表上所有的查询缓存都会被清空。 所以,对于频繁更新的表来说,查询缓存的效率非常低,这时使用查询缓存就有点得不偿失了。
可以将参数 query_cache_type
设置成 DEMAND
,这样对于默认的SQL语句都不使用查询缓存。而对于确定要使用查询缓存的语句,可以用 SQL_CACHE 来显式的指定,如 select SQL_CACHE * from t where id=1;
在 MySQL 8.0 版本之后就没有查询缓存这个功能了。
分析器
来到第三步:分析器。
分析器会对 SQL 语句进行词法分析,识别出里面的字符串分别是什么,代表什么。
MySQL 把输入的 select 这个关键字识别出来,这是一个查询语句。它也要把字符串“t”识别成“表名t”,把字符串“id”识别成“列id”。
然后分析器会对 SQL 语句做语法分析。分析器根据语法规则,判断这个 SQL 语句是否满足 MySQL 的语法规则。若是语法不正确,会返回 You have an error in your SQL syntax
的错误。
优化器
接下来来到第四步:优化器。
优化器的作用是在表里面有多个索引时,决定使用哪个索引;或者在一个语句有多表关联,即使用 join 的时候,决定各个表的连接顺序。优化器阶段完成后,这个语句的执行方案就确定下来了。
执行器
然后进入第五步:执行器。
在开始执行的时候,要先判断一下这个登录用户对这个表 t 有没有执行查询的权限,如果没有,就会返回没有权限的错误。如果是语句命中了查询缓存,会在查询缓存返回结果的时候,做权限验证。
如果有权限,就会继续执行语句。找到对应的表并打开表,执行器就会根据表的引擎去使用这个引擎提供的接口。
比如这个例子中的表 t 中,id 字段没有索引,使用的引擎是 InnoDB,那么执行器的执行流程是这样的:
- 调用 InnoDB 引擎接口取这个表的第一行,判断 id 值是不是1,如果不是则跳过,如果是则将这行存在结果集中;
- 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
- 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
至此,这个语句就执行完成了。
若是有索引的情况。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。
总结
根据上面的介绍,SQL 语句总体的执行顺序:连接器 -> (查询缓存)-> 分析器 -> 优化器 -> 执行器。
下面是一条查询语句执行过程的流程图:
总结:
- 首先使用登录命令登录 MySQL,登录后,输入一条查询语句;
- 执行后,会首先查询缓存,若缓存中有对应的数据,直接返回,若没有,则会找分析器进行下一步操作,注意,只有在开启查询缓存时才会执行这一步,MySQL 8.0 版本后就没有查询缓存了,语句会直接走分析器;
- 分析器首先对 SQL 语句进行词法分析,根据输入的 select,判断这条语句是查询语句,并将后面的字符串识别成对应的表名与列名,随后会对 SQL 语句进行语法分析,判断这条语句是否符合 MySQL 的语法规则,若符合,则会进入优化器阶段;
- 优化器会根据语句来选择这条语句的具体执行方案,然后交给执行器执行;
- 执行器会判断用户对要使用的表有没有执行查询的权限,若没有权限,就会返回没有权限的错误。如果是语句命中了查询缓存这种情况,会在查询缓存返回结果的时候,做权限验证。如果有权限,就会继续执行语句。找到对应的表并打开表,执行器就会根据表的引擎去使用这个引擎提供的接口,执行语句,根据 id 这列有没有索引会分为两种情况:
- 没有索引的情况:
- 调用 InnoDB 引擎接口取这个表的第一行,判断 id 值是不是1,如果不是则跳过,如果是则将这行存在结果集中;
- 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
- 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
- 有索引的情况:
- 调用 InnoDB 引擎接口取满足条件的第一行这个接口,将这行存在结果集中;
- 之后循环取满足条件的下一行这个接口,直到取到满足条件的最后一行;
- 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
- 没有索引的情况:
- 至此,查询语句执行结束
参考资料
《MySQL 必知必会》
《MySQL 45讲》