MySQL在整体架构上,被划分为连接器、解析器、优化器、执行引擎、查询缓存等多个核心部件:
连接器
以连接池的方式管理客户端的连接,也就是说,MySQL会为每一个连接创建或复用一条线程来处理其命令,可以通过以下SQL查看所有线程的信息:
SHOW PROCESSLIST;
执行该SQL语句,得到结果:
| id | User | Host | db | Command | Time | State | Info |
| 13 | root | localhost:63617 | Query | 0 | init | SHOW PROCESSLIST |
其中id,User,Host列的意义非常容易理解,这里就不再过多介绍了。
db字段表示当前正在访问的数据库,如果没有调用过USEE命令,基本上就会为空。
Command是非常重要的列,表示当前客户端正在执行的操作类型。该列可能的值有非常多,可参考官方文档中8.14.2 Thread Command Values部分,这里只介绍几个常见的值:
Daemon: 表示该线程是MySQL服务器内部所使用的,而非客户端线程;Create DB: 表示正在执行创建表的操作;Drop DB: 表示正在执行删除表的操作;Init DB: 表示线程正在选择其默认数据库(也就是在执行USE操作);Prepare: 表示该线程正在准备一条Prepare Statement语句;Query:表示正在执行一条语句,注意这里没有限定一定是查询语句;Sleep: 表示该线程正在等待客户端发出新的指令;Time: 表示该线程目前未被使用;
接下来是Time列,表示该线程处于当前状态的时间,单位是秒。
State列也是非常重要的列,表示该线程的状态。同Command列一样,State列可能出现的值也有非常多,可参考官方文档中8.14.3 General Thread States部分。常见的值有以下几种:
checking permissions: 表示该线程正在检查服务器是否有执行该语句的权限;starting: 表示语句开始执行的第一个阶段;update: 表示线程准备开始更新表;Updating: 表示线程正在搜索被更新的列,或正在更新这些列;Writing to net: 表示服务器正在向网络写入数据包;
最后是Info列,表示当前正在执行的语句。有时候语句太长,Info会显示不全,这时候可以考虑使用SHOW FULL PROCESSLIT语句来显示完整的Info信息。
解析器
或称分析器。解析器主要完成两项工作:词法分析和语法分析,最后生成"解析树"交由之后的组件进行处理。
词法分析是将整个SQL语句识别为一组"符号"并识别其中的关键字用于判断该SQL的目的是"做什么"。如以下SQL:
SELECT * FROM USER WHERE ID = 1;
该SQL会被解析为8个"符号",并被识别为一个从USER表执行查询的查询语句。
词法分析完成之后,接着会进行语法分析。语法分析的主要工作是用来检查输入的SQL是否符合MySQL 的语法。如:
-- SELECT 关键字写错了
SELEC * FROM USER WHERE ID = 1;
执行该SQL会出现错误,提示:You have an error in your SQL syntax...
这就是语法分析器所分析的结果。
优化器
优化器的主要工作是根据"解析树"生成最优的执行计划。根据官方文档 MySQL Internals Manual: Chapter 7 The Optimizer中的介绍,优化工作分为主要优化和其它优化两个类别。
主要优化包括:常量关系优化,连接优化,换位,排序优化和分组优化。
其它优化包括:空值过滤和分区优化。
相关信息量非常庞大,建议直接参考官方文档。
执行引擎
执行引擎负责SQL语句的执行。其会根据SQL语句中所涉及表的存储引擎类型,与其对应的API接口进行交互,得到查询结果并返回给客户端。
查询缓存
这是一个在MySQL8中已经被删除的组件。目前可以在官方网站中MySQL 5.7 Reference Manual: 8.10.3 The MySQL Query Cache部分查询到对应的信息。且在该文档中高亮提示:
The query cache is deprecated as of MySQL 5.7.20, and is removed in MySQL 8.0.
当查询缓存被开启时,执行引擎会在将结果返回给客户端之前,将以完整的查询语句为Key,查询结果为Value的形式将查询结果缓存起来。
当新的查询语句提交到MySQL 时,查询缓存会逐字节检查新的查询语句,当新查询语句与缓存中的Key完全相同时,才会直接返回缓存中的值。这个完全相同的条件非常严格,包括大小写一致,如以下两条SQL不能被视为同一条SQL而使用查询缓存:
SELECT * FROM USER;
select * from user;
另外,即使是参数相同的情况下,预编译语句和普通查询语句也不能被识别为同一条SQL:
SELECT * FROM USER WHERE ID = ?; -- 参数值为1
SELECT * FROM USER WHERE ID = 1;
包括使用了部分函数,如:NOW(),RAND()等,都无法被缓存。
同时,查询缓存很容易被清除:只要某一张表有被执行INSERT,UPDATE,DELETE,TRUNCATE TABLE,ALTER TABLE,DROP TABLE或DROP DATABASE操作,该表所对应的所有缓存都会被无效化并从查询缓存组件中删除。
综上所述,查询缓存使用条件苛刻,易清理,从而导致在实际使用过程中缓存的命中率并不高。MySQL核心开发成员Matt Lord在一篇博客中提到了MySQL8删除掉查询缓存的原因,也可以从中了解到查询缓存的诸多弊端,请见这里。
SQL执行流程
MySQL通过这些核心组件,串接起了SQL语句的执行流程:
① 客户端与MySQL 服务器建立连接,并提交SQL语句;
② (可选)当查询缓存可用并开启的情况下,查询缓存会逐字节检查SQL语句,寻找对应的缓存结果。当缓存命中时,直接返回查询结果;
③ SQL语句提交到了解析器中,解析器按顺序执行词法解析和语法解析,生成解析树;
④ 优化器根据解析树中的信息,生成最佳执行计划。期间会经过主要优化和其它优化这两种方式的优化;
⑤ 执行引擎根据执行计划对相关表的存储引擎类型,调用对应的API接口,查询对应数据;
⑥ 存储引擎操作底层数据文件,获取到查询结果并响应给执行引擎;
⑦(可选)当查询缓存可用并开启的情况下,执行引擎将查询结果缓存到查询缓存中;
⑧ 执行引擎返回查询结果;